mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(management): cover the new control plane route in CI's two guards
Both failures are from this branch, not pre-existing The component allowlist test asserts the gateway and backend route sets union to the whole app, so any route on neither is a 404 on both pods. Allowlist the `/management/v1/` prefix on the backend, next to the other control plane entries, so every resource that moves under it later is covered without a per-resource edit The otel handler test builds its request as a SimpleNamespace carrying only `state`. The validation handler now reads `request.url.path` to decide whether the caller is on a surface with its own error contract, so the fake needs a url; a real Request always has one, which is why the handler does not guard for it The control plane branch returns early, and nothing covered that it still closes the dangling SERVER span first, so those requests would have leaked a span apiece. Added a case that pins it; removing the close call fails it
This commit is contained in:
parent
cf127e16e8
commit
b7a3516232
2 changed files with 33 additions and 8 deletions
|
|
@ -70,6 +70,10 @@ BACKEND_PATH_PREFIXES: tuple[str, ...] = (
|
|||
"/project/",
|
||||
"/memory/",
|
||||
"/mcp/",
|
||||
# Control plane (see the List Endpoints + Tables standard). Every resource
|
||||
# eventually moves under this prefix, so allowlist it once rather than
|
||||
# per-resource.
|
||||
"/management/v1/",
|
||||
# Spend / analytics
|
||||
"/spend/",
|
||||
"/analytics/",
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ from litellm.integrations._types.open_inference import ErrorAttributes
|
|||
from ._helpers import assert_server_span_attrs, get_server_span
|
||||
|
||||
|
||||
def _fake_request(parent_otel_span=None):
|
||||
def _fake_request(parent_otel_span=None, path="/key/generate"):
|
||||
"""A real Request always carries a url; the validation handler reads its path to
|
||||
decide whether the caller is on a surface with its own error contract."""
|
||||
state = types.SimpleNamespace()
|
||||
if parent_otel_span is not None:
|
||||
state.parent_otel_span = parent_otel_span
|
||||
return types.SimpleNamespace(state=state)
|
||||
return types.SimpleNamespace(state=state, url=types.SimpleNamespace(path=path))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -41,7 +43,7 @@ def wired_otel(otel_with_exporter, monkeypatch):
|
|||
def test_close_dangling_span_stamps_status(
|
||||
wired_otel, server_span_factory, status, path
|
||||
):
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path))
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path), path=path)
|
||||
_close_dangling_otel_server_span(request, status)
|
||||
assert_server_span_attrs(
|
||||
wired_otel,
|
||||
|
|
@ -59,7 +61,7 @@ def test_close_dangling_span_noop_when_no_span(wired_otel):
|
|||
|
||||
def test_close_dangling_span_noop_when_otel_absent(server_span_factory, monkeypatch):
|
||||
monkeypatch.setattr(proxy_server_module, "open_telemetry_logger", None)
|
||||
request = _fake_request(parent_otel_span=server_span_factory("/key/generate"))
|
||||
request = _fake_request(parent_otel_span=server_span_factory("/key/generate"), path="/key/generate")
|
||||
_close_dangling_otel_server_span(request, 500)
|
||||
|
||||
|
||||
|
|
@ -83,7 +85,7 @@ def test_close_dangling_span_noop_when_otel_absent(server_span_factory, monkeypa
|
|||
def test_exception_handler_closes_span(
|
||||
wired_otel, server_span_factory, handler, exc, status, path
|
||||
):
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path))
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path), path=path)
|
||||
response = asyncio.run(handler(request, exc))
|
||||
assert response.status_code == status
|
||||
assert_server_span_attrs(
|
||||
|
|
@ -94,6 +96,25 @@ def test_exception_handler_closes_span(
|
|||
)
|
||||
|
||||
|
||||
def test_validation_handler_closes_span_on_the_control_plane_too(wired_otel, server_span_factory):
|
||||
"""The control plane answers validation errors with a 400 problem document
|
||||
instead of the proxy-wide 422, and that branch returns early. It must still
|
||||
close the dangling SERVER span, or those requests leak a span apiece."""
|
||||
path = "/management/v1/spend_logs/end_users"
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path), path=path)
|
||||
|
||||
response = asyncio.run(otel_request_validation_exception_handler(request, RequestValidationError(errors=[])))
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.media_type == "application/problem+json"
|
||||
assert_server_span_attrs(
|
||||
wired_otel,
|
||||
expected_status=400,
|
||||
expected_url_path=path,
|
||||
where="otel_request_validation_exception_handler (control plane)",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["/team/list", "/organization/list"])
|
||||
def test_openai_exception_handler_stamps_structured_error_on_span(
|
||||
wired_otel, server_span_factory, path
|
||||
|
|
@ -103,7 +124,7 @@ def test_openai_exception_handler_stamps_structured_error_on_span(
|
|||
ProxyException stringified to "" so error.message was dropped — the span
|
||||
showed an error with no message."""
|
||||
msg = "Authentication Error, Invalid proxy server token passed."
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path))
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path), path=path)
|
||||
exc = ProxyException(message=msg, type="auth_error", param="key", code=401)
|
||||
|
||||
response = asyncio.run(openai_exception_handler(request, exc))
|
||||
|
|
@ -123,7 +144,7 @@ def test_openai_exception_handler_stamps_structured_error_on_span(
|
|||
|
||||
def test_unhandled_handler_reraises_known_exceptions(wired_otel, server_span_factory):
|
||||
"""ProxyException / HTTPException / RequestValidationError have dedicated handlers."""
|
||||
request = _fake_request(parent_otel_span=server_span_factory("/key/generate"))
|
||||
request = _fake_request(parent_otel_span=server_span_factory("/key/generate"), path="/key/generate")
|
||||
with pytest.raises(HTTPException):
|
||||
asyncio.run(
|
||||
otel_unhandled_exception_handler(
|
||||
|
|
@ -147,7 +168,7 @@ def test_unhandled_handler_reraises_known_exceptions(wired_otel, server_span_fac
|
|||
def test_openai_exception_handler_closes_span(
|
||||
wired_otel, server_span_factory, code, path
|
||||
):
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path))
|
||||
request = _fake_request(parent_otel_span=server_span_factory(path), path=path)
|
||||
exc = ProxyException(
|
||||
message="boom",
|
||||
type="invalid_request_error",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue