mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
test(router): mark credential scope regressions xfail
This commit is contained in:
parent
e6f86d7a06
commit
704beae881
3 changed files with 120 additions and 24 deletions
|
|
@ -296,6 +296,11 @@ async def test_create__model_encoded_file_id(harness):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="batch creation omits the authenticated team ID",
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("body", "expected_model_id"),
|
||||
[
|
||||
|
|
@ -1260,6 +1265,11 @@ async def test_retrieve__model_encoded_id(retrieve_harness):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="batch retrieval omits the authenticated team ID",
|
||||
)
|
||||
async def test_retrieve__model_encoded_id__scopes_credentials_to_callers_team(retrieve_harness):
|
||||
await call_retrieve(
|
||||
retrieve_harness,
|
||||
|
|
@ -1813,6 +1823,11 @@ async def test_list__model_from_body_routes_and_encodes(list_harness):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="batch listing omits the authenticated team ID",
|
||||
)
|
||||
async def test_list__model_from_body__scopes_credentials_to_callers_team(list_harness):
|
||||
await call_list(
|
||||
list_harness,
|
||||
|
|
@ -2135,6 +2150,11 @@ async def test_cancel__model_encoded_id(cancel_harness):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="batch cancellation omits the authenticated team ID",
|
||||
)
|
||||
async def test_cancel__model_encoded_id__scopes_credentials_to_callers_team(cancel_harness):
|
||||
await call_cancel(
|
||||
cancel_harness,
|
||||
|
|
|
|||
|
|
@ -1979,6 +1979,11 @@ def test_get_file_content_routed_provider_skips_streaming_when_resolved_provider
|
|||
proxy_logging_obj.post_call_failure_hook.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="empty named credentials discard deployment metadata",
|
||||
)
|
||||
def test_get_file_content_keeps_bedrock_metadata_for_empty_named_credential(mocker: MockerFixture, monkeypatch):
|
||||
import litellm.proxy.proxy_server as ps
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
|
@ -2015,7 +2020,7 @@ def test_get_file_content_keeps_bedrock_metadata_for_empty_named_credential(mock
|
|||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", router)
|
||||
proxy_logging_obj.update_request_status = mocker.AsyncMock()
|
||||
proxy_logging_obj.post_call_failure_hook = mocker.AsyncMock()
|
||||
provider_call = mocker.patch.object(
|
||||
provider_call = mocker.patch.object( # test-quality-ok: provider call is the endpoint boundary
|
||||
litellm,
|
||||
"afile_content",
|
||||
new=mocker.AsyncMock(
|
||||
|
|
@ -2520,8 +2525,28 @@ def test_list_files_resolves_wildcard_deployment_credentials(
|
|||
("caller_team_id", "expected_status", "expected_api_key"),
|
||||
[
|
||||
pytest.param("team-b", 200, "team-b-key", id="owner"),
|
||||
pytest.param("team-a", 400, None, id="other-team"),
|
||||
pytest.param(None, 400, None, id="teamless"),
|
||||
pytest.param(
|
||||
"team-a",
|
||||
400,
|
||||
None,
|
||||
id="other-team",
|
||||
marks=pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="file listing omits the authenticated team ID",
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
None,
|
||||
400,
|
||||
None,
|
||||
id="teamless",
|
||||
marks=pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="authenticated teamless callers use unscoped lookup",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_list_files_scopes_exact_deployment_id_to_owning_team(
|
||||
|
|
@ -2564,7 +2589,9 @@ def test_list_files_scopes_exact_deployment_id_to_owning_team(
|
|||
proxy_logging_obj.update_request_status = mocker.AsyncMock()
|
||||
proxy_logging_obj.post_call_success_hook = mocker.AsyncMock(return_value=[])
|
||||
proxy_logging_obj.post_call_failure_hook = mocker.AsyncMock()
|
||||
provider_call = mocker.patch.object(litellm, "afile_list", new=mocker.AsyncMock(return_value=[]))
|
||||
provider_call = mocker.patch.object( # test-quality-ok: provider call is the endpoint boundary
|
||||
litellm, "afile_list", new=mocker.AsyncMock(return_value=[])
|
||||
)
|
||||
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
api_key="test-key",
|
||||
|
|
|
|||
|
|
@ -4520,19 +4520,34 @@ def test_get_deployment_credentials_with_provider_includes_bucket_name():
|
|||
{"other": {"api_key": "other-key"}},
|
||||
{"api_key": "inline-key"},
|
||||
{"api_key": "inline-key"},
|
||||
id="stale-name-keeps-inline-key",
|
||||
id="missing-inline-fallback",
|
||||
marks=pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="missing named credentials discard inline auth",
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
{},
|
||||
{"aws_role_name": "deployment-role", "aws_region_name": "us-west-2"},
|
||||
{"aws_role_name": "deployment-role", "aws_region_name": "us-west-2"},
|
||||
id="empty-worker-keeps-deployment-auth",
|
||||
id="missing-deployment-auth-fallback",
|
||||
marks=pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="missing named credentials discard deployment auth",
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
{"saved": {}},
|
||||
{},
|
||||
{},
|
||||
id="empty-saved-credential-allows-ambient-auth",
|
||||
id="present-empty-allows-ambient-auth",
|
||||
marks=pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="empty named credentials are treated as missing",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
@ -4565,7 +4580,9 @@ def test_get_deployment_credentials_with_provider_named_credential_states(
|
|||
for name, values in credential_values_by_name.items()
|
||||
]
|
||||
|
||||
with patch.object(litellm, "credential_list", credential_list):
|
||||
with patch.object( # test-quality-ok: CredentialAccessor reads the process-global credential list
|
||||
litellm, "credential_list", credential_list
|
||||
):
|
||||
result = router.get_deployment_credentials_with_provider(model_id="configured-model")
|
||||
|
||||
if expected_auth is None:
|
||||
|
|
@ -4800,7 +4817,19 @@ def test_get_deployment_credentials_with_provider_no_fallback_to_other_team_only
|
|||
)
|
||||
|
||||
|
||||
def test_get_deployment_credentials_with_provider_rejects_other_team_exact_id():
|
||||
@pytest.mark.parametrize(
|
||||
("team_id", "expected_api_key"),
|
||||
[
|
||||
pytest.param("team-b", "team-b-key", id="owner"),
|
||||
pytest.param("team-a", None, id="foreign-team"),
|
||||
pytest.param(None, "team-b-key", id="unscoped-compatibility"),
|
||||
pytest.param("", None, id="empty-team-id"),
|
||||
],
|
||||
)
|
||||
def test_get_deployment_credentials_with_provider_scopes_exact_id(
|
||||
team_id: str | None,
|
||||
expected_api_key: str | None,
|
||||
) -> None:
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
|
|
@ -4817,24 +4846,24 @@ def test_get_deployment_credentials_with_provider_rejects_other_team_exact_id():
|
|||
],
|
||||
)
|
||||
|
||||
owner_credentials = router.get_deployment_credentials_with_provider(
|
||||
model_id="team-b-deployment", team_id="team-b"
|
||||
)
|
||||
assert owner_credentials is not None
|
||||
assert owner_credentials["api_key"] == "team-b-key"
|
||||
|
||||
unscoped_credentials = router.get_deployment_credentials_with_provider(model_id="team-b-deployment")
|
||||
assert unscoped_credentials is not None
|
||||
assert unscoped_credentials["api_key"] == "team-b-key"
|
||||
|
||||
assert (
|
||||
router.get_deployment_credentials_with_provider(
|
||||
model_id="team-b-deployment", team_id="team-a"
|
||||
)
|
||||
is None
|
||||
credentials = router.get_deployment_credentials_with_provider(
|
||||
model_id="team-b-deployment",
|
||||
team_id=team_id,
|
||||
)
|
||||
|
||||
if expected_api_key is None:
|
||||
assert credentials is None
|
||||
return
|
||||
|
||||
assert credentials is not None
|
||||
assert credentials["api_key"] == expected_api_key
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="a rejected exact ID falls through to shared wildcards",
|
||||
)
|
||||
def test_foreign_exact_deployment_id_is_not_reinterpreted_as_a_shared_wildcard():
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
|
|
@ -4861,6 +4890,11 @@ def test_foreign_exact_deployment_id_is_not_reinterpreted_as_a_shared_wildcard()
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="a shared model name wins before the team's public name",
|
||||
)
|
||||
def test_team_public_model_name_beats_colliding_shared_model_name_for_owner():
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
|
|
@ -4986,6 +5020,11 @@ def test_get_deployment_credentials_with_provider_skips_other_team_wildcard():
|
|||
assert owner_credentials["api_key"] == "team-b-key"
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="a foreign specific wildcard shadows the shared fallback",
|
||||
)
|
||||
def test_foreign_specific_wildcard_falls_back_to_broader_shared_wildcard():
|
||||
credentials = _router_with_foreign_specific_and_shared_wildcards().get_deployment_credentials_with_provider(
|
||||
model_id="openai/gpt-5.2", team_id="team-a"
|
||||
|
|
@ -6451,6 +6490,11 @@ def test_access_group_block_via_litellm_model_branch_does_not_use_default_fallba
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=pytest.fail.Exception,
|
||||
reason="normal routing bypasses team scope for exact deployment IDs",
|
||||
)
|
||||
def test_common_checks_rejects_foreign_exact_deployment_id_for_team():
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
|
||||
|
|
@ -6485,6 +6529,11 @@ def test_common_checks_rejects_foreign_exact_deployment_id_for_team():
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
raises=AssertionError,
|
||||
reason="normal routing selects a foreign specific wildcard",
|
||||
)
|
||||
def test_common_checks_skips_foreign_specific_wildcard_for_team():
|
||||
_, deployments = _router_with_foreign_specific_and_shared_wildcards()._common_checks_available_deployment(
|
||||
model="openai/gpt-5.2",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue