mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-25 05:54:53 +00:00
* test(integration): stop GET /__observations from draining the upstream log GET /__observations used to empty the queue, so a second read in a test saw nothing and a "never reached upstream" check could pass whether or not the request was forwarded. GET now returns every request since the last reset and DELETE /__observations is the reset. An autouse fixture resets the worker upstream before each test, which also covers tests that read without the gateway fixture. Discarded reads used as resets become DELETEs, helpers that accumulated drained reads now read one snapshot, and tests with several phases reset between them. The three vacuous negative checks (rag allowlist, guardrail opt-out chaos, xAI batch output) now assert the absence on the same read that shows a request which did reach the upstream. * style(integration): space the observations unit test like its neighbours
91 lines
3.8 KiB
Python
91 lines
3.8 KiB
Python
import uuid
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import httpx
|
|
import pytest
|
|
import yaml
|
|
from integration._support.client import Gateway, gateway_from_environment
|
|
from integration._support.process import owned_proxy
|
|
from pydantic import JsonValue
|
|
|
|
pytestmark: Final = pytest.mark.timeout(180)
|
|
|
|
|
|
def _deployment(model_name: str, model: str, upstream_url: str) -> dict[str, JsonValue]:
|
|
return {
|
|
"model_name": model_name,
|
|
"litellm_params": {"model": model, "api_base": f"{upstream_url}/v1", "api_key": "synthetic-wildcard-key"},
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def candidate(tmp_path_factory: pytest.TempPathFactory) -> Iterator[Gateway]:
|
|
directory: Final = tmp_path_factory.mktemp("wildcard-access")
|
|
with gateway_from_environment() as base:
|
|
config: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
|
|
config["model_list"] = [
|
|
_deployment("*", "openai/*", base.upstream_url),
|
|
_deployment("anthropic/*", "openai/*", base.upstream_url),
|
|
_deployment("groq/*", "openai/*", base.upstream_url),
|
|
_deployment("good-model", "openai/good-model-upstream", base.upstream_url),
|
|
]
|
|
path: Final = directory / "wildcard-access.yaml"
|
|
path.write_text(yaml.safe_dump(config))
|
|
with owned_proxy(base, directory, {}, config=path) as proxy:
|
|
yield proxy
|
|
|
|
|
|
@pytest.fixture
|
|
def upstream(candidate: Gateway) -> Iterator[httpx.Client]:
|
|
with httpx.Client(base_url=candidate.upstream_url, timeout=5, trust_env=False) as client:
|
|
client.delete("/__observations").raise_for_status()
|
|
yield client
|
|
|
|
|
|
def _chat(gateway: Gateway, model: str, key: str) -> httpx.Response:
|
|
return gateway.request(
|
|
"POST",
|
|
"/v1/chat/completions",
|
|
{"model": model, "messages": [{"role": "user", "content": f"wildcard {uuid.uuid4().hex}"}]},
|
|
key=key,
|
|
)
|
|
|
|
|
|
def _observed_models(upstream: httpx.Client) -> list[JsonValue]:
|
|
return [request["body"]["model"] for request in upstream.get("/__observations").json()["requests"]]
|
|
|
|
|
|
def test_an_all_models_key_reaches_a_model_served_only_by_the_catch_all_deployment(
|
|
candidate: Gateway, upstream: httpx.Client
|
|
) -> None:
|
|
with candidate.scenario() as scenario:
|
|
key: Final = scenario.key(models=["*"])
|
|
unlisted: Final = f"unlisted-{uuid.uuid4().hex}"
|
|
response: Final = _chat(candidate, unlisted, key)
|
|
assert response.status_code == 200, response.text
|
|
assert _observed_models(upstream) == [unlisted]
|
|
|
|
|
|
def test_a_key_without_models_inherits_the_users_exact_and_wildcard_grants(
|
|
candidate: Gateway, upstream: httpx.Client
|
|
) -> None:
|
|
with candidate.scenario() as scenario:
|
|
user_id: Final = scenario.user(models=["good-model", "anthropic/*"])
|
|
key: Final = scenario.key(user_id=user_id, models=[])
|
|
wildcard_model: Final = f"claude-{uuid.uuid4().hex}"
|
|
assert _chat(candidate, f"anthropic/{wildcard_model}", key).status_code == 200
|
|
assert _chat(candidate, "good-model", key).status_code == 200
|
|
assert _observed_models(upstream) == [wildcard_model, "good-model-upstream"]
|
|
upstream.delete("/__observations").raise_for_status()
|
|
denied: Final = tuple(
|
|
_chat(candidate, outside, key)
|
|
for outside in (f"groq/{wildcard_model}", f"bedrock/anthropic.{wildcard_model}")
|
|
)
|
|
assert [(response.status_code, response.json()["error"]["type"]) for response in denied] == [
|
|
(403, "user_model_access_denied")
|
|
] * 2, [response.text for response in denied]
|
|
assert _observed_models(upstream) == []
|
|
assert _chat(candidate, f"groq/{wildcard_model}", candidate.key).status_code == 200
|
|
assert _observed_models(upstream) == [wildcard_model]
|