From 16e8a5c33a40f6743ace6f1f5a3836ea1477d869 Mon Sep 17 00:00:00 2001 From: mateo Date: Tue, 7 Jul 2026 20:24:37 +0000 Subject: [PATCH] test(e2e): pin teamless all-team-models key to full proxy access Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../access_control/access_control_client.py | 1 + .../access_control/test_access_control_e2e.py | 37 +++++++++++++++++++ tests/e2e/e2e_gateway.py | 16 ++++++++ tests/e2e/models.py | 8 ++++ 4 files changed, 62 insertions(+) diff --git a/tests/e2e/access_control/access_control_client.py b/tests/e2e/access_control/access_control_client.py index d7bc9c280aa..2ea060145b8 100644 --- a/tests/e2e/access_control/access_control_client.py +++ b/tests/e2e/access_control/access_control_client.py @@ -17,6 +17,7 @@ from models import ( MODEL_ACCESS_DENIED_MARKER = "key_model_access_denied" ROUTE_NOT_ALLOWED_MARKER = "not allowed to call this route" +ALL_TEAM_MODELS_SENTINEL = "all-team-models" @dataclass(frozen=True, slots=True) diff --git a/tests/e2e/access_control/test_access_control_e2e.py b/tests/e2e/access_control/test_access_control_e2e.py index ce649fa2400..5ef73564561 100644 --- a/tests/e2e/access_control/test_access_control_e2e.py +++ b/tests/e2e/access_control/test_access_control_e2e.py @@ -19,6 +19,7 @@ import pytest from access_control_client import ( AccessControlClient, + ALL_TEAM_MODELS_SENTINEL, MODEL_ACCESS_DENIED_MARKER, ROUTE_NOT_ALLOWED_MARKER, ) @@ -29,6 +30,7 @@ pytestmark = pytest.mark.e2e ALLOWED_MODEL = "gemini-2.5-flash" DISALLOWED_MODEL = "gpt-5.5" +PROXY_CHAT_MODELS = frozenset({"gpt-5.5", "claude-haiku-4-5", "gemini-2.5-flash"}) def _is_json(body: str) -> bool: @@ -81,3 +83,38 @@ class TestAccessControl: f"{result.status_code}: {result.body[:300]}" ) assert _is_json(result.body), f"400 body must be valid JSON: {result.body[:300]}" + + +class TestTeamlessAllTeamModels: + """A key scoped to ``all-team-models`` with no team assigned inherits the + full proxy model list, exactly as if its models field were left empty. This + is the intended contract (GH #30737): #29746 tightened it to deny such keys + and was reverted in #32032. These cases pin both surfaces a client touches, + listing (GET /v1/models) and calling (/chat/completions), so re-introducing + the team_id guard on either path fails here.""" + + def test_teamless_all_team_models_key_lists_all_proxy_models( + self, client: AccessControlClient, resources: ResourceManager + ) -> None: + key = resources.key(models=[ALL_TEAM_MODELS_SENTINEL]) + listed = set(client.gateway.list_models(key)) + assert PROXY_CHAT_MODELS <= listed, ( + f"teamless {ALL_TEAM_MODELS_SENTINEL!r} key must list every proxy model, " + f"missing {PROXY_CHAT_MODELS - listed}; got {sorted(listed)}" + ) + assert ALL_TEAM_MODELS_SENTINEL not in listed, ( + f"the {ALL_TEAM_MODELS_SENTINEL!r} sentinel must expand to real models, " + f"never surface as a listed model; got {sorted(listed)}" + ) + + def test_teamless_all_team_models_key_can_call_any_model( + self, client: AccessControlClient, resources: ResourceManager + ) -> None: + key = resources.key(models=[ALL_TEAM_MODELS_SENTINEL]) + result = client.chat_status( + key, ALLOWED_MODEL, f"capital of France? {unique_marker()}" + ) + assert result.status_code == 200, ( + f"teamless {ALL_TEAM_MODELS_SENTINEL!r} key must be allowed to call " + f"{ALLOWED_MODEL!r}, got {result.status_code}: {result.body[:300]}" + ) diff --git a/tests/e2e/e2e_gateway.py b/tests/e2e/e2e_gateway.py index 055d06d1c79..4fcd04e430c 100644 --- a/tests/e2e/e2e_gateway.py +++ b/tests/e2e/e2e_gateway.py @@ -39,6 +39,7 @@ from models import ( ModelInfoBody, ModelInfoEntry, ModelInfoResponse, + ModelListResponse, ModelMode, ModelNewBody, ModelNewResponse, @@ -119,6 +120,21 @@ class Gateway: ) ).data + def list_models(self, key: str) -> list[str]: + """The public model names GET /v1/models lists as callable for `key`, + the same visibility an OpenAI-compatible client (e.g. OpenWebUI) sees.""" + return [ + entry.id + for entry in unwrap( + self.transport.get( + "/v1/models", + headers=self.transport.bearer(key), + params=NoBody(), + response_type=ModelListResponse, + ) + ).data + ] + def create_model( self, model_name: str, diff --git a/tests/e2e/models.py b/tests/e2e/models.py index 0490db286ea..bf8979f0d71 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -336,6 +336,14 @@ class ModelInfoResponse(BaseModel): data: list[ModelInfoEntry] = [] +class ModelListEntry(BaseModel): + id: str + + +class ModelListResponse(BaseModel): + data: list[ModelListEntry] = [] + + class FileEntry(BaseModel): id: str