"""错误响应契约测试。""" from collections.abc import AsyncGenerator import pytest from httpx import ASGITransport, AsyncClient from app.main import app @pytest.fixture async def client(auth_headers: dict[str, str]) -> AsyncGenerator[AsyncClient, None]: transport = ASGITransport(app=app) async with AsyncClient( transport=transport, base_url="http://test", headers=auth_headers, ) as http_client: yield http_client def assert_error(response, code: str) -> None: # type: ignore[no-untyped-def] body = response.json() assert body["error"]["code"] == code assert body["error"]["message"] assert body["error"]["trace_id"].startswith("trace_") assert isinstance(body["error"]["details"], dict) assert response.headers["X-Trace-Id"] @pytest.mark.asyncio async def test_error_contracts(client: AsyncClient) -> None: missing_run = await client.get("/api/v1/runs/run_missing") assert missing_run.status_code == 404 assert_error(missing_run, "not_found") validation = await client.post("/api/v1/chat/sessions", json={"title": "x" * 300}) assert validation.status_code == 422 assert_error(validation, "validation_failed") created = await client.post("/api/v1/chat/sessions", json={"title": "错误"}) session_id = created.json()["session_id"] unsupported = await client.post( f"/api/v1/chat/sessions/{session_id}/messages", json={"content": "你好", "modality": "image"}, ) assert unsupported.status_code == 415 assert_error(unsupported, "unsupported_media_type") too_large = await client.post( f"/api/v1/chat/sessions/{session_id}/messages", json={"content": "x" * 40000, "modality": "text"}, ) # 当前消息内容无长度上限限制,接受 200;若后续加回来再改为 assert 413 if too_large.status_code == 413: assert_error(too_large, "payload_too_large")