diff --git a/managed-chatkit/backend/app/main.py b/managed-chatkit/backend/app/main.py index 4106d0b21..3b9a6914c 100644 --- a/managed-chatkit/backend/app/main.py +++ b/managed-chatkit/backend/app/main.py @@ -67,10 +67,10 @@ async def create_session(request: Request) -> JSONResponse: payload = parse_json(upstream) if not upstream.is_success: - message = None - if isinstance(payload, Mapping): - message = payload.get("error") - message = message or upstream.reason_phrase or "Failed to create session" + message = resolve_error_message( + payload, + fallback=upstream.reason_phrase or "Failed to create session", + ) return respond({"error": message}, upstream.status_code, cookie_value) client_secret = None @@ -93,6 +93,29 @@ async def create_session(request: Request) -> JSONResponse: ) +def resolve_error_message(payload: Mapping[str, Any], fallback: str) -> str: + """Return a human-readable error string from an upstream payload.""" + raw_error = payload.get("error") + + if isinstance(raw_error, str) and raw_error.strip(): + return raw_error.strip() + + if isinstance(raw_error, Mapping): + message = raw_error.get("message") + if isinstance(message, str) and message.strip(): + return message.strip() + + code = raw_error.get("code") + if isinstance(code, str) and code.strip(): + return code.strip() + + top_level_message = payload.get("message") + if isinstance(top_level_message, str) and top_level_message.strip(): + return top_level_message.strip() + + return fallback + + def respond( payload: Mapping[str, Any], status_code: int, cookie_value: str | None = None ) -> JSONResponse: diff --git a/managed-chatkit/backend/tests/test_main.py b/managed-chatkit/backend/tests/test_main.py new file mode 100644 index 000000000..e58f3f9df --- /dev/null +++ b/managed-chatkit/backend/tests/test_main.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from fastapi.testclient import TestClient +import httpx + +from app import main + + +def test_resolve_error_message_prefers_nested_error_message() -> None: + payload = {"error": {"message": "Workflow id is invalid"}} + + message = main.resolve_error_message(payload, fallback="Fallback") + + assert message == "Workflow id is invalid" + + +def test_resolve_error_message_uses_top_level_error_string() -> None: + payload = {"error": "Rate limit exceeded"} + + message = main.resolve_error_message(payload, fallback="Fallback") + + assert message == "Rate limit exceeded" + + +def test_create_session_flattens_nested_upstream_error(monkeypatch) -> None: + class StubAsyncClient: + def __init__(self, *args, **kwargs) -> None: + pass + + async def __aenter__(self) -> "StubAsyncClient": + return self + + async def __aexit__(self, exc_type, exc, tb) -> bool: + return False + + async def post(self, *args, **kwargs) -> httpx.Response: + request = httpx.Request( + "POST", "https://api.openai.com/v1/chatkit/sessions" + ) + return httpx.Response( + status_code=400, + json={"error": {"message": "Workflow not found"}}, + request=request, + ) + + monkeypatch.setenv("OPENAI_API_KEY", "test-key") + monkeypatch.setattr(main.httpx, "AsyncClient", StubAsyncClient) + + client = TestClient(main.app) + response = client.post("/api/create-session", json={"workflow": {"id": "wf_123"}}) + + assert response.status_code == 400 + assert response.json() == {"error": "Workflow not found"} + assert "chatkit_session_id=" in response.headers.get("set-cookie", "")