mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
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>
This commit is contained in:
parent
a43f128a74
commit
16e8a5c33a
4 changed files with 62 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue