This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit ff4ae16826174a2bbab6d188f663aa078a76987f
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Apr 15 10:59:28 2026 +0530

    [v3-2-test] Feat/check multi team enabled when team name provided api 
(#63994) (#65226)
    
    * Add API check to ensure multi team is enabled when team_name is provided
    
    * remove unnecessary arguments in added tests
    
    * add variable tests and add slight change to other tests to align with 
variables test file
    
    * Change error message, Modify tests, Add bulk tests, Fix CI issues
    (cherry picked from commit 62711897b3be6c097fb6ccd2f3d4362a5beacd9d)
    
    Co-authored-by: ahilashsasidharan 
<[email protected]>
---
 .../api_fastapi/core_api/datamodels/connections.py | 11 ++-
 .../api_fastapi/core_api/datamodels/pools.py       | 19 ++++-
 .../api_fastapi/core_api/datamodels/variables.py   |  9 +++
 .../airflow/ui/src/queries/useEditConnection.tsx   |  6 ++
 .../src/airflow/ui/src/queries/useEditVariable.ts  |  3 +-
 .../core_api/routes/public/test_connections.py     | 93 +++++++++++++++++++++-
 .../core_api/routes/public/test_pools.py           | 93 +++++++++++++++++++++-
 .../core_api/routes/public/test_variables.py       | 82 +++++++++++++++++++
 8 files changed, 311 insertions(+), 5 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/connections.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/connections.py
index 0b69c47cfdf..680376cddac 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/connections.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/connections.py
@@ -21,11 +21,12 @@ import json
 from collections.abc import Iterable, Mapping
 from typing import Annotated, Any
 
-from pydantic import Field, field_validator
+from pydantic import Field, field_validator, model_validator
 from pydantic_core.core_schema import ValidationInfo
 
 from airflow._shared.secrets_masker import redact, should_hide_value_for_key
 from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel, 
make_partial_model
+from airflow.configuration import conf
 
 
 # Response Models
@@ -199,5 +200,13 @@ class ConnectionBody(StrictBaseModel):
             )
         return v
 
+    @model_validator(mode="after")
+    def validate_team_name(self) -> ConnectionBody:
+        if self.team_name is not None and not conf.getboolean("core", 
"multi_team"):
+            raise ValueError(
+                "team_name cannot be set when multi_team mode is disabled. 
Please contact your administrator."
+            )
+        return self
+
 
 ConnectionBodyPartial = make_partial_model(ConnectionBody)
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
index a661308d443..55c7c3ad35e 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/pools.py
@@ -20,9 +20,10 @@ from __future__ import annotations
 from collections.abc import Callable, Iterable
 from typing import Annotated
 
-from pydantic import BeforeValidator, Field
+from pydantic import BeforeValidator, Field, model_validator
 
 from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel
+from airflow.configuration import conf
 
 
 def _call_function(function: Callable[[], int]) -> int:
@@ -83,6 +84,14 @@ class PoolPatchBody(StrictBaseModel):
     include_deferred: bool | None = None
     team_name: str | None = Field(max_length=50, default=None)
 
+    @model_validator(mode="after")
+    def validate_team_name(self) -> PoolPatchBody:
+        if self.team_name is not None and not conf.getboolean("core", 
"multi_team"):
+            raise ValueError(
+                "team_name cannot be set when multi_team mode is disabled. 
Please contact your administrator."
+            )
+        return self
+
 
 class PoolBody(BasePool, StrictBaseModel):
     """Pool serializer for post bodies."""
@@ -91,3 +100,11 @@ class PoolBody(BasePool, StrictBaseModel):
     description: str | None = None
     include_deferred: bool = False
     team_name: str | None = Field(max_length=50, default=None)
+
+    @model_validator(mode="after")
+    def validate_team_name(self) -> PoolBody:
+        if self.team_name is not None and not conf.getboolean("core", 
"multi_team"):
+            raise ValueError(
+                "team_name cannot be set when multi_team mode is disabled. 
Please contact your administrator."
+            )
+        return self
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/variables.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/variables.py
index 9dc7969b69b..001d8c70e95 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/variables.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/variables.py
@@ -24,6 +24,7 @@ from pydantic import Field, JsonValue, model_validator
 
 from airflow._shared.secrets_masker import redact
 from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel, 
make_partial_model
+from airflow.configuration import conf
 from airflow.models.base import ID_LEN
 from airflow.typing_compat import Self
 
@@ -60,6 +61,14 @@ class VariableBody(StrictBaseModel):
     description: str | None = Field(default=None)
     team_name: str | None = Field(max_length=50, default=None)
 
+    @model_validator(mode="after")
+    def validate_team_name(self) -> VariableBody:
+        if self.team_name is not None and not conf.getboolean("core", 
"multi_team"):
+            raise ValueError(
+                "team_name cannot be set when multi_team mode is disabled. 
Please contact your administrator."
+            )
+        return self
+
 
 VariableBodyPartial = make_partial_model(VariableBody)
 
diff --git a/airflow-core/src/airflow/ui/src/queries/useEditConnection.tsx 
b/airflow-core/src/airflow/ui/src/queries/useEditConnection.tsx
index 54f5cef48c2..0b7b9cefd0a 100644
--- a/airflow-core/src/airflow/ui/src/queries/useEditConnection.tsx
+++ b/airflow-core/src/airflow/ui/src/queries/useEditConnection.tsx
@@ -89,6 +89,11 @@ export const useEditConnection = (
     if (requestBody.schema !== initialConnection.schema) {
       updateMask.push("schema");
     }
+    if (requestBody.team_name !== initialConnection.team_name) {
+      updateMask.push("team_name");
+    }
+
+    const teamName = requestBody.team_name === "" ? undefined : 
requestBody.team_name;
 
     mutate({
       connectionId: initialConnection.connection_id,
@@ -99,6 +104,7 @@ export const useEditConnection = (
         extra: requestBody.extra === "{}" ? undefined : requestBody.extra,
         // eslint-disable-next-line unicorn/no-null
         port: requestBody.port === "" ? null : Number(requestBody.port),
+        team_name: teamName,
       },
       updateMask,
     });
diff --git a/airflow-core/src/airflow/ui/src/queries/useEditVariable.ts 
b/airflow-core/src/airflow/ui/src/queries/useEditVariable.ts
index 73a0fcab7dc..ae339930581 100644
--- a/airflow-core/src/airflow/ui/src/queries/useEditVariable.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useEditVariable.ts
@@ -78,12 +78,13 @@ export const useEditVariable = (
 
     const parsedDescription =
       editVariableRequestBody.description === "" ? undefined : 
editVariableRequestBody.description;
+    const teamName = editVariableRequestBody.team_name === "" ? undefined : 
editVariableRequestBody.team_name;
 
     mutate({
       requestBody: {
         description: parsedDescription,
         key: editVariableRequestBody.key,
-        team_name: editVariableRequestBody.team_name,
+        team_name: teamName,
         value: editVariableRequestBody.value,
       },
       updateMask,
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
index 4ca21b0ff49..a37ae170676 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
@@ -34,6 +34,7 @@ from airflow.utils.session import NEW_SESSION, provide_session
 
 from tests_common.test_utils.api_fastapi import _check_last_log
 from tests_common.test_utils.asserts import assert_queries_count
+from tests_common.test_utils.config import conf_vars
 from tests_common.test_utils.db import clear_db_connections, clear_db_logs, 
clear_test_connections
 from tests_common.test_utils.markers import 
skip_if_force_lowest_dependencies_marker
 
@@ -299,10 +300,15 @@ class TestPostConnection(TestConnectionEndpoint):
         assert len(connection) == 1
         _check_last_log(session, dag_id=None, event="post_connection", 
logical_date=None)
 
+    @conf_vars({("core", "multi_team"): "True"})
     def test_post_should_respond_201_with_team(self, test_client, session, 
testing_team):
         response = test_client.post(
             "/connections",
-            json={"connection_id": TEST_CONN_ID, "conn_type": TEST_CONN_TYPE, 
"team_name": testing_team.name},
+            json={
+                "connection_id": TEST_CONN_ID,
+                "conn_type": TEST_CONN_TYPE,
+                "team_name": testing_team.name,
+            },
         )
         assert response.status_code == 201
         assert response.json() == {
@@ -351,6 +357,22 @@ class TestPostConnection(TestConnectionEndpoint):
             ]
         }
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_post_rejects_team_name_when_multi_team_disabled(self, 
test_client):
+        response = test_client.post(
+            "/connections",
+            json={
+                "connection_id": TEST_CONN_ID_2,
+                "conn_type": TEST_CONN_TYPE_2,
+                "team_name": "test_team",
+            },
+        )
+        assert response.status_code == 422
+        assert (
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in response.json()["detail"][0]["msg"]
+        )
+
     @pytest.mark.parametrize(
         "body",
         [
@@ -615,6 +637,7 @@ class TestPatchConnection(TestConnectionEndpoint):
 
         assert response.json() == expected_result
 
+    @conf_vars({("core", "multi_team"): "True"})
     def test_patch_with_team_should_respond_200(self, test_client, 
testing_team, session):
         self.create_connection()
 
@@ -979,6 +1002,23 @@ class TestPatchConnection(TestConnectionEndpoint):
         )
         assert response.status_code == 422
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_patch_rejects_team_name_when_multi_team_disabled(self, 
test_client):
+        self.create_connection()
+        response = test_client.patch(
+            f"/connections/{TEST_CONN_ID_2}",
+            json={
+                "connection_id": TEST_CONN_ID_2,
+                "conn_type": "new_type",
+                "team_name": "test_team",
+            },
+        )
+        assert response.status_code == 422
+        assert (
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in response.json()["detail"][0]["msg"]
+        )
+
 
 class TestConnection(TestConnectionEndpoint):
     def setup_method(self):
@@ -1629,6 +1669,57 @@ class TestBulkConnections(TestConnectionEndpoint):
 
         assert sorted(results.success) == [TEST_CONN_ID, TEST_CONN_ID_2]
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_bulk_rejects_team_name_when_multi_team_is_disabled(self, 
test_client):
+        actions = {
+            "actions": [
+                {
+                    "action": "create",
+                    "entities": [
+                        {
+                            "connection_id": "test_conn_id_1",
+                            "conn_type": TEST_CONN_TYPE,
+                            "description": "description",
+                        },
+                        {
+                            "connection_id": "test_conn_id_2",
+                            "conn_type": TEST_CONN_TYPE_2,
+                            "description": "description_2",
+                            "team_name": "test_team",
+                        },
+                    ],
+                },
+                {
+                    "action": "update",
+                    "entities": [
+                        {
+                            "connection_id": "test_conn_id_3",
+                            "conn_type": TEST_CONN_TYPE,
+                            "description": "updated_description",
+                            "team_name": "test_team",
+                        },
+                        {
+                            "connection_id": "test_conn_id_4",
+                            "conn_type": TEST_CONN_TYPE_2,
+                            "description": "updated_description_2",
+                        },
+                    ],
+                },
+            ]
+        }
+        response = test_client.patch("/connections", json=actions)
+        assert response.status_code == 422
+        detail = response.json()["detail"]
+
+        assert all(
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in err["msg"]
+            for err in detail
+        ), f"Unexpected errors in detail: {detail}"
+
+        expected_error_conn_ids = {err["input"]["connection_id"] for err in 
detail}
+        assert sorted(expected_error_conn_ids) == ["test_conn_id_2", 
"test_conn_id_3"]
+
 
 class TestPostConnectionExtraBackwardCompatibility(TestConnectionEndpoint):
     def test_post_should_accept_empty_string_as_extra(self, test_client, 
session):
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
index e2ef8720735..08aa24cd767 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py
@@ -25,6 +25,7 @@ from airflow.models.pool import Pool
 from airflow.models.team import Team
 from airflow.utils.session import provide_session
 
+from tests_common.test_utils.config import conf_vars
 from tests_common.test_utils.db import clear_db_pools, clear_db_teams
 from tests_common.test_utils.logs import check_last_log
 
@@ -416,8 +417,27 @@ class TestPatchPool(TestPoolsEndpoint):
         assert response.json() == expected_response
         check_last_log(session, dag_id=None, event="patch_pool", 
logical_date=None)
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_patch_pool_rejects_team_name_when_multi_team_disabled(self, 
test_client):
+        self.create_pools()
+        response = test_client.patch(
+            f"/pools/{POOL2_NAME}",
+            json={
+                "name": POOL2_NAME,
+                "slots": POOL2_SLOT,
+                "include_deferred": POOL2_INCLUDE_DEFERRED,
+                "team_name": "test_team",
+            },
+        )
+        assert response.status_code == 422
+        assert (
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in response.json()["detail"][0]["msg"]
+        )
+
 
 class TestPostPool(TestPoolsEndpoint):
+    @conf_vars({("core", "multi_team"): "True"})
     @pytest.mark.parametrize(
         ("body", "expected_status_code", "expected_response"),
         [
@@ -483,9 +503,10 @@ class TestPostPool(TestPoolsEndpoint):
     def test_should_respond_200(self, test_client, session, body, 
expected_status_code, expected_response):
         self.create_pools()
         n_pools = session.scalar(select(func.count()).select_from(Pool))
+
         response = test_client.post("/pools", json=body)
-        assert response.status_code == expected_status_code
 
+        assert response.status_code == expected_status_code
         assert response.json() == expected_response
         assert session.scalar(select(func.count()).select_from(Pool)) == 
n_pools + 1
         check_last_log(session, dag_id=None, event="post_pool", 
logical_date=None)
@@ -523,6 +544,22 @@ class TestPostPool(TestPoolsEndpoint):
         )
         assert response.status_code == 422
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_post_pool_rejects_team_name_when_multi_team_disabled(self, 
test_client):
+        response = test_client.post(
+            "/pools",
+            json={
+                "name": "bad_team_pool",
+                "slots": 1,
+                "team_name": "test_team",
+            },
+        )
+        assert response.status_code == 422
+        assert (
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in response.json()["detail"][0]["msg"]
+        )
+
     def test_should_respond_401(self, unauthenticated_test_client):
         response = unauthenticated_test_client.post("/pools", json={})
         assert response.status_code == 401
@@ -590,6 +627,7 @@ class TestPostPool(TestPoolsEndpoint):
 
 
 class TestBulkPools(TestPoolsEndpoint):
+    @conf_vars({("core", "multi_team"): "True"})
     @pytest.mark.enable_redact
     @pytest.mark.parametrize(
         ("actions", "expected_results"),
@@ -1045,7 +1083,9 @@ class TestBulkPools(TestPoolsEndpoint):
     )
     def test_bulk_pools(self, test_client, actions, expected_results, session):
         self.create_pools()
+
         response = test_client.patch("/pools", json=actions)
+
         response_data = response.json()
         for key, value in expected_results.items():
             assert response_data[key] == value
@@ -1105,3 +1145,54 @@ class TestBulkPools(TestPoolsEndpoint):
             },
         )
         assert response.status_code == 403
+
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_bulk_rejects_team_name_when_multi_team_is_disabled(self, 
test_client):
+        actions = {
+            "actions": [
+                {
+                    "action": "create",
+                    "entities": [
+                        {
+                            "name": "pool_1",
+                            "slots": 1,
+                            "description": "description",
+                        },
+                        {
+                            "name": "pool_2",
+                            "slots": 2,
+                            "description": "description_2",
+                            "team_name": "test_team",
+                        },
+                    ],
+                },
+                {
+                    "action": "update",
+                    "entities": [
+                        {
+                            "name": "pool_3",
+                            "slots": 3,
+                            "description": "updated_description",
+                            "team_name": "test_team",
+                        },
+                        {
+                            "name": "pool_4",
+                            "slots": 4,
+                            "description": "updated_description_2",
+                        },
+                    ],
+                },
+            ]
+        }
+        response = test_client.patch("/pools", json=actions)
+        assert response.status_code == 422
+        detail = response.json()["detail"]
+
+        assert all(
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in err["msg"]
+            for err in detail
+        ), f"Unexpected errors in detail: {detail}"
+
+        expected_error_names = {err["input"]["name"] for err in detail}
+        assert sorted(expected_error_names) == ["pool_2", "pool_3"]
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_variables.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_variables.py
index 50871879675..045b78acc56 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_variables.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_variables.py
@@ -521,6 +521,21 @@ class TestPatchVariable(TestVariableEndpoint):
         body = response.json()
         assert f"The Variable with key: `{TEST_VARIABLE_KEY}` was not found" 
== body["detail"]
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_patch_rejects_team_name_when_multi_team_disabled(self, 
test_client):
+        body = {
+            "key": TEST_VARIABLE_KEY,
+            "value": "The new value",
+            "description": "The new description",
+            "team_name": "test_team",
+        }
+        response = test_client.patch(f"/variables/{TEST_VARIABLE_KEY}", 
json=body)
+        assert response.status_code == 422
+        assert (
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in response.json()["detail"][0]["msg"]
+        )
+
     @pytest.mark.enable_redact
     def test_patch_with_update_mask_description_only(self, test_client, 
session):
         """PATCH with update_mask=['description'] should only update 
description, keeping value unchanged."""
@@ -686,6 +701,21 @@ class TestPostVariable(TestVariableEndpoint):
             ]
         }
 
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_post_rejects_team_name_when_multi_team_disabled(self, 
test_client):
+        body = {
+            "key": "new variable key",
+            "value": "new variable value",
+            "description": "new variable description",
+            "team_name": "test_team",
+        }
+        response = test_client.post("/variables", json=body)
+        assert response.status_code == 422
+        assert (
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in response.json()["detail"][0]["msg"]
+        )
+
     @pytest.mark.parametrize(
         "body",
         [
@@ -1366,3 +1396,55 @@ class TestBulkVariables(TestVariableEndpoint):
             },
         )
         assert response.status_code == 403
+
+    @conf_vars({("core", "multi_team"): "False"})
+    def test_bulk_rejects_team_name_when_multi_team_is_disabled(self, 
test_client):
+        actions = {
+            "actions": [
+                {
+                    "action": "create",
+                    "entities": [
+                        {
+                            "key": "var_1",
+                            "value": "value_1",
+                            "description": "description",
+                        },
+                        {
+                            "key": "var_2",
+                            "value": "value_2",
+                            "description": "description_2",
+                            "team_name": "test_team",
+                        },
+                    ],
+                },
+                {
+                    "action": "update",
+                    "entities": [
+                        {
+                            "key": "var_3",
+                            "value": "value_3",
+                            "description": "updated_description",
+                            "team_name": "test_team",
+                        },
+                        {
+                            "key": "var_4",
+                            "value": "value_4",
+                            "description": "updated_description_2",
+                        },
+                    ],
+                },
+            ]
+        }
+        response = test_client.patch("/variables", json=actions)
+
+        assert response.status_code == 422
+        detail = response.json()["detail"]
+
+        assert all(
+            "team_name cannot be set when multi_team mode is disabled. Please 
contact your administrator."
+            in err["msg"]
+            for err in detail
+        ), f"Unexpected errors in detail: {detail}"
+
+        expected_error_keys = {err["input"]["key"] for err in detail}
+        assert sorted(expected_error_keys) == ["var_2", "var_3"]

Reply via email to