83 lines
2.7 KiB
Python
83 lines
2.7 KiB
Python
import json
|
|
from typing import Optional
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.testclient import TestClient
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.middleware.logging import LoggingMiddleware
|
|
from app.middleware.errors import register_exception_handlers
|
|
|
|
|
|
class Item(BaseModel):
|
|
name: str = Field(..., min_length=3)
|
|
quantity: int = Field(..., ge=1)
|
|
|
|
|
|
def build_test_app() -> FastAPI:
|
|
app = FastAPI()
|
|
app.add_middleware(LoggingMiddleware, log_requests=False, log_responses=False)
|
|
register_exception_handlers(app)
|
|
|
|
@app.get("/http-error")
|
|
async def http_error():
|
|
raise HTTPException(status_code=403, detail="Forbidden action")
|
|
|
|
@app.post("/validation")
|
|
async def validation_endpoint(item: Item): # noqa: F841
|
|
return {"ok": True}
|
|
|
|
@app.get("/crash")
|
|
async def crash():
|
|
raise RuntimeError("Boom")
|
|
|
|
return app
|
|
|
|
|
|
def assert_envelope(resp, status: int, code: str, has_details: bool, expected_cid: Optional[str] = None):
|
|
assert resp.status_code == status
|
|
data = resp.json()
|
|
assert data["success"] is False
|
|
assert data["error"]["status"] == status
|
|
assert data["error"]["code"] == code
|
|
assert isinstance(data["error"]["message"], str) and data["error"]["message"]
|
|
if has_details:
|
|
assert "details" in data["error"]
|
|
else:
|
|
assert "details" not in data["error"]
|
|
|
|
# Correlation id in body and header
|
|
assert "correlation_id" in data and isinstance(data["correlation_id"], str)
|
|
header_cid = resp.headers.get("X-Correlation-ID")
|
|
assert header_cid == data["correlation_id"]
|
|
if expected_cid is not None:
|
|
assert header_cid == expected_cid
|
|
|
|
|
|
def test_http_exception_envelope_and_correlation_id_echo():
|
|
app = build_test_app()
|
|
client = TestClient(app)
|
|
cid = "abc-12345-test"
|
|
resp = client.get("/http-error", headers={"X-Correlation-ID": cid})
|
|
assert_envelope(resp, 403, "http_error", has_details=False, expected_cid=cid)
|
|
|
|
|
|
def test_validation_exception_envelope_and_correlation_id_echo():
|
|
app = build_test_app()
|
|
client = TestClient(app)
|
|
cid = "cid-validation-67890"
|
|
# Missing fields to trigger 422
|
|
resp = client.post("/validation", json={"name": "ab"}, headers={"X-Correlation-ID": cid})
|
|
assert_envelope(resp, 422, "validation_error", has_details=True, expected_cid=cid)
|
|
|
|
|
|
def test_unhandled_exception_envelope_and_generated_correlation_id():
|
|
app = build_test_app()
|
|
client = TestClient(app, raise_server_exceptions=False)
|
|
resp = client.get("/crash")
|
|
# Should have generated a correlation id and not echo None
|
|
assert_envelope(resp, 500, "internal_error", has_details=False)
|
|
assert resp.headers.get("X-Correlation-ID")
|
|
|
|
|