mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-16 23:41:43 +00:00
* test(e2e): migrate access-control and inference-endpoint regression tests Move the access-control and non-chat inference-endpoint cases from litellm-regression-tests onto the shared e2e harness so a regression in either fails here first access_control/ asserts the gateway's authorization and error-shape contract: a key limited to one model is denied 403 (key_model_access_denied) when it calls another, a key scoped to allowed_routes=["llm_api_routes"] is forbidden 403 from a management route, and an unknown model is rejected 400 before any provider is called. The source asserted 401 for the disallowed-model case against an older proxy; the live contract is now a 403, so the guard tracks current behavior llm_translation/ gains one file per non-chat inference endpoint (/v1/responses, /v1/messages, /embeddings, /v1/rerank, /v1/audio/speech, /v1/images/generations). Each test registers the deployment it needs through /model/new, drives real provider traffic, asserts the parsed body carries real content instead of just a 200, then deletes the model on teardown, so nothing is hardcoded into the gateway config * Update endpoints_client.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
83 lines
3 KiB
Python
83 lines
3 KiB
Python
"""Live e2e: the gateway's authorization and error-shape contract.
|
|
|
|
A virtual key may only call models in its allow-list and route groups in its
|
|
allowed_routes; both denials are a 403 raised before any provider is touched. A
|
|
syntactically valid request naming a non-existent model is a 400 with a JSON body,
|
|
never forwarded and never a 5xx. Migrated from
|
|
litellm-regression-tests/tests/test_access_control.py: the source asserted 401 for
|
|
the disallowed-model case against an older proxy, but the current contract
|
|
(auth_checks.py) is a 403 key_model_access_denied, and the unknown-route check is
|
|
replaced by a stronger route-permission check (an llm-only key rejected from a
|
|
management route).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from access_control_client import (
|
|
AccessControlClient,
|
|
MODEL_ACCESS_DENIED_MARKER,
|
|
ROUTE_NOT_ALLOWED_MARKER,
|
|
)
|
|
from e2e_config import unique_marker
|
|
from lifecycle import ResourceManager
|
|
|
|
pytestmark = pytest.mark.e2e
|
|
|
|
ALLOWED_MODEL = "gemini-2.5-flash"
|
|
DISALLOWED_MODEL = "gpt-5.5"
|
|
|
|
|
|
def _is_json(body: str) -> bool:
|
|
try:
|
|
json.loads(body)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
class TestAccessControl:
|
|
def test_disallowed_model_is_denied_403(
|
|
self, client: AccessControlClient, resources: ResourceManager
|
|
) -> None:
|
|
key = resources.key(models=[ALLOWED_MODEL])
|
|
result = client.chat_status(
|
|
key, DISALLOWED_MODEL, f"capital of France? {unique_marker()}"
|
|
)
|
|
assert result.status_code == 403, (
|
|
f"key limited to {ALLOWED_MODEL!r} calling {DISALLOWED_MODEL!r} must be "
|
|
f"denied 403, got {result.status_code}: {result.body[:300]}"
|
|
)
|
|
assert MODEL_ACCESS_DENIED_MARKER in result.body, (
|
|
f"403 body must be a model-access denial, got: {result.body[:300]}"
|
|
)
|
|
|
|
def test_llm_only_key_forbidden_from_management_route_403(
|
|
self, client: AccessControlClient, resources: ResourceManager
|
|
) -> None:
|
|
key = client.llm_only_key()
|
|
resources.defer(lambda: client.delete_key(key))
|
|
result = client.create_model_status(key, f"e2e-forbidden-{unique_marker()}")
|
|
assert result.status_code == 403, (
|
|
f"llm-only key calling a management route must be denied 403, got "
|
|
f"{result.status_code}: {result.body[:300]}"
|
|
)
|
|
assert ROUTE_NOT_ALLOWED_MARKER in result.body, (
|
|
f"403 body must be a route-permission denial, got: {result.body[:300]}"
|
|
)
|
|
|
|
def test_unknown_model_returns_400(
|
|
self, client: AccessControlClient, resources: ResourceManager
|
|
) -> None:
|
|
key = resources.key()
|
|
result = client.chat_status(
|
|
key, f"nonexistent-model-{unique_marker()}", "hi this is a test"
|
|
)
|
|
assert result.status_code == 400, (
|
|
f"unknown model must be rejected 400 before forwarding, got "
|
|
f"{result.status_code}: {result.body[:300]}"
|
|
)
|
|
assert _is_json(result.body), f"400 body must be valid JSON: {result.body[:300]}"
|