mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
test(agent_session_endpoints): assert v2 error envelope shape in tests
* conftest.py registers register_v2_exception_handlers on the test app so the test client sees the same envelope as production. * Existing test assertions on res.json()["detail"] updated to res.json()["error"]["message"] for /v2 endpoints. * New test_error_envelope.py covers 404 + 422 + 409 status mapping.
This commit is contained in:
parent
c67ca5b299
commit
c958cc3f88
4 changed files with 91 additions and 2 deletions
|
|
@ -247,6 +247,9 @@ def _build_test_app(
|
|||
run_router,
|
||||
session_router,
|
||||
)
|
||||
from litellm.proxy.agent_session_endpoints.error_envelope import (
|
||||
register_v2_exception_handlers,
|
||||
)
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
|
||||
app = FastAPI()
|
||||
|
|
@ -254,6 +257,8 @@ def _build_test_app(
|
|||
app.include_router(session_router)
|
||||
app.include_router(run_router)
|
||||
app.include_router(internal_router)
|
||||
# Mirror the production setup so tests see the same /v2 error envelope.
|
||||
register_v2_exception_handlers(app)
|
||||
|
||||
def _fake_auth() -> UserAPIKeyAuth:
|
||||
return UserAPIKeyAuth(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
"""
|
||||
Verify the standardized {error: {code, message, status, details?}} envelope
|
||||
applies to /v2/* endpoints (HTTPException + ValidationError) and that
|
||||
non-/v2 paths keep FastAPI's default detail shape.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_404_emits_envelope_for_missing_session(client, noop_provider):
|
||||
"""A 404 from the v2 surface should be wrapped in the envelope."""
|
||||
res = client.get(
|
||||
"/v2/sessions/does-not-exist",
|
||||
headers={"Authorization": "Bearer k"},
|
||||
)
|
||||
# find_unique returns None -> ownership assert raises HTTPException(404).
|
||||
assert res.status_code == 404
|
||||
body = res.json()
|
||||
assert "error" in body and "detail" not in body
|
||||
assert body["error"]["status"] == 404
|
||||
assert body["error"]["code"] == "not_found"
|
||||
assert isinstance(body["error"]["message"], str)
|
||||
|
||||
|
||||
def test_validation_error_wraps_in_envelope(client, noop_provider):
|
||||
"""A 422 ValidationError from a missing required body field should
|
||||
arrive as ``{error: {code: validation_error, details: [...]}}``."""
|
||||
# POST /v2/agents requires `name` + `model`; omit both.
|
||||
res = client.post(
|
||||
"/v2/agents",
|
||||
headers={"Authorization": "Bearer k"},
|
||||
json={},
|
||||
)
|
||||
assert res.status_code == 422
|
||||
body = res.json()
|
||||
assert "error" in body and "detail" not in body
|
||||
assert body["error"]["code"] == "validation_error"
|
||||
assert body["error"]["status"] == 422
|
||||
assert isinstance(body["error"].get("details"), list)
|
||||
# Validation details preserve the per-field {loc, msg, type} shape.
|
||||
assert any("name" in str(d.get("loc", [])) for d in body["error"]["details"])
|
||||
|
||||
|
||||
def test_http_status_code_to_envelope_code_mapping(client, noop_provider):
|
||||
"""409 should map to ``conflict``, not a generic name."""
|
||||
# Create an agent + session, then try to POST with missing agent_id
|
||||
# path -> simpler: trigger a 409 by creating two runs back-to-back
|
||||
# on the same session.
|
||||
agent = client.post(
|
||||
"/v2/agents",
|
||||
headers={"Authorization": "Bearer k"},
|
||||
json={"name": "t", "model": "gpt-4"},
|
||||
).json()
|
||||
sess = client.post(
|
||||
f"/v2/agents/{agent['id']}/sessions",
|
||||
headers={"Authorization": "Bearer k"},
|
||||
json={"repos": []},
|
||||
).json()
|
||||
sid = sess["id"]
|
||||
daemon_token = sess["daemon_token"]
|
||||
client.post(
|
||||
f"/v2/sessions/{sid}/internal/register",
|
||||
headers={"Authorization": f"Bearer {daemon_token}"},
|
||||
json={"vm_id": "i-noop"},
|
||||
)
|
||||
# First run: queued.
|
||||
client.post(
|
||||
f"/v2/sessions/{sid}/runs",
|
||||
headers={"Authorization": "Bearer k"},
|
||||
json={"prompt": {"text": "hi"}},
|
||||
)
|
||||
# Second run on same session while first is still queued -> 409 run_busy.
|
||||
res = client.post(
|
||||
f"/v2/sessions/{sid}/runs",
|
||||
headers={"Authorization": "Bearer k"},
|
||||
json={"prompt": {"text": "again"}},
|
||||
)
|
||||
assert res.status_code == 409
|
||||
body = res.json()
|
||||
assert body["error"]["status"] == 409
|
||||
assert body["error"]["code"] == "conflict"
|
||||
assert "run_busy" in body["error"]["message"]
|
||||
|
|
@ -90,7 +90,8 @@ def test_followup_returns_409_when_active_run_is_not_the_latest(
|
|||
json={"prompt": {"text": "should be blocked"}},
|
||||
)
|
||||
assert res.status_code == 409
|
||||
assert "run_busy" in res.json()["detail"]
|
||||
# Error envelope shape: {"error": {"code", "message", ...}}
|
||||
assert "run_busy" in res.json()["error"]["message"]
|
||||
|
||||
|
||||
def test_followup_normal_path_creates_run_when_no_active_runs(client, noop_provider):
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ def test_view_only_admin_cannot_create_agent(view_only_admin_client, noop_provid
|
|||
json={"name": "evil", "model": "gpt-4"},
|
||||
)
|
||||
assert res.status_code == 403
|
||||
assert "view-only" in res.json()["detail"].lower()
|
||||
# Error envelope: {"error": {"code", "message", "status"}}
|
||||
assert "view-only" in res.json()["error"]["message"].lower()
|
||||
|
||||
|
||||
def test_view_only_admin_cannot_update_other_tenant_agent(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue