mirror of
https://github.com/fusion44/blitz_api.git
synced 2026-08-13 11:52:45 +02:00
Route HTTPException (app- and framework-raised), RequestValidationError
and uncaught exceptions through build_error_response so every error body
is {detail, error_code, report?, trace?}. Fixes the bare-string body that
string HTTPException details produced, and registers the handler on the
Starlette base HTTPException so framework 404/405/415 also carry the full
envelope.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
import json
|
|
|
|
from fastapi import HTTPException
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
from app.api.models import ErrorMessage
|
|
|
|
|
|
def _body(resp):
|
|
return json.loads(bytes(resp.body))
|
|
|
|
|
|
def test_framework_http_exception_uses_error_message_shape():
|
|
# Framework-generated errors (404/405/...) are raised as the Starlette base
|
|
# HTTPException, not fastapi's subclass. They must still get the full flat
|
|
# ErrorMessage envelope, not FastAPI's default bare {"detail": "..."}.
|
|
client = TestClient(main.app) # no `with`: don't trigger lifespan/background tasks
|
|
resp = client.get("/this-route-does-not-exist")
|
|
assert resp.status_code == 404
|
|
body = resp.json()
|
|
assert isinstance(body["detail"], str)
|
|
assert body["detail"] == "Not Found"
|
|
assert body["error_code"] == ""
|
|
assert body["report"] is None
|
|
assert body["trace"] is None
|
|
|
|
|
|
async def test_string_detail_becomes_object():
|
|
resp = await main.http_e_handler(None, HTTPException(400, detail="bad password"))
|
|
assert resp.status_code == 400
|
|
body = _body(resp)
|
|
assert body["detail"] == "bad password" # NOT a bare string body
|
|
assert body["error_code"] == ""
|
|
|
|
|
|
async def test_errormessage_detail_gates_report_by_default():
|
|
detail = ErrorMessage(
|
|
detail="app failed",
|
|
error_code="app_status_update_failed",
|
|
report="/home/blitzapi/frame",
|
|
).model_dump()
|
|
resp = await main.http_e_handler(None, HTTPException(500, detail=detail))
|
|
body = _body(resp)
|
|
assert body["detail"] == "app failed"
|
|
assert body["error_code"] == "app_status_update_failed"
|
|
assert body["report"] is None # sensitive report gated off by default
|
|
|
|
|
|
async def test_validation_handler_keeps_field_array():
|
|
exc = RequestValidationError(
|
|
[
|
|
{
|
|
"type": "missing",
|
|
"loc": ["body", "password"],
|
|
"msg": "Field required",
|
|
"input": None,
|
|
}
|
|
]
|
|
)
|
|
resp = await main.valid_e_handler(None, exc)
|
|
body = _body(resp)
|
|
assert resp.status_code == 422
|
|
assert body["detail"] == "Field required"
|
|
assert body["error_code"] == "invalid_request_input"
|
|
assert body["report"] is not None # validation field array always kept
|
|
|
|
|
|
async def test_catchall_returns_error_message():
|
|
resp = await main.unhandled_e_handler(None, ValueError("boom"))
|
|
body = _body(resp)
|
|
assert resp.status_code == 500
|
|
assert body["error_code"] == "unknown"
|
|
assert body["report"] is None # gated off by default
|