diff --git a/database/Validation/TEST008-Error-Handling-Middleware-Testing.md b/database/Validation/TEST008-Error-Handling-Middleware-Testing.md new file mode 100644 index 0000000..65fd73d --- /dev/null +++ b/database/Validation/TEST008-Error-Handling-Middleware-Testing.md @@ -0,0 +1,70 @@ +# TEST008 - Error Handling Middleware Testing + +This document records the testing evidence and result for ticket **TEST008**. + +## Ticket intent + +- Trigger API errors. +- Check response format. +- Confirm consistent error responses. +- Confirm proper status codes. + +## Scope + +Validated backend error handling behavior implemented in: + +- `database/logging_system/request_middleware.py` +- `database/logging_system/exception_handler.py` + +Automated tests added in: + +- `test/test_t1008_error_handling_middleware.py` + +## Test cases implemented + +1. **Success path includes request traceability** + - Call `GET /ok`. + - Expect HTTP `200`. + - Expect body: `{"ok": true}`. + - Expect `X-Request-ID` response header is present. + +2. **Error path returns consistent format and status** + - Call `GET /explode` (forced runtime exception). + - Expect HTTP `500`. + - Expect body includes: + - `message` = `"Internal server error"` + - `request_id` (non-empty) + - Expect `X-Request-ID` header equals body `request_id`. + +## Fix applied during testing + +While executing TEST008, one assertion failed because error responses did not include the `X-Request-ID` header. +To align success and error behavior, `global_exception_handler` was updated to set: + +- `headers={"X-Request-ID": request_id}` + +in the returned `JSONResponse`. + +## Execution evidence + +Command run from repository root: + +```bash +python -m pytest test/test_t1008_error_handling_middleware.py -q +``` + +Observed result: + +```text +.. [100%] +2 passed in 0.30s +``` + +## Final result + +TEST008 acceptance criteria are satisfied by automated tests: + +- API errors are triggered and validated. +- Error response format is consistent. +- Proper status codes are returned (`200` success, `500` unhandled error). +- `request_id` tracing is consistent in both payload and response header. diff --git a/database/logging_system/exception_handler.py b/database/logging_system/exception_handler.py index e12359c..d512862 100644 --- a/database/logging_system/exception_handler.py +++ b/database/logging_system/exception_handler.py @@ -38,4 +38,5 @@ async def global_exception_handler(request: Request, exc: Exception) -> JSONResp "message": "Internal server error", "request_id": request_id, }, + headers={"X-Request-ID": request_id}, ) diff --git a/test/test_t1008_error_handling_middleware.py b/test/test_t1008_error_handling_middleware.py new file mode 100644 index 0000000..a2a1797 --- /dev/null +++ b/test/test_t1008_error_handling_middleware.py @@ -0,0 +1,50 @@ +import pytest + +fastapi = pytest.importorskip("fastapi") +pytest.importorskip("starlette") + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from database.logging_system.exception_handler import global_exception_handler +from database.logging_system.request_middleware import RequestLoggingMiddleware + + +def _build_test_app() -> FastAPI: + app = FastAPI() + app.add_middleware(RequestLoggingMiddleware) + app.add_exception_handler(Exception, global_exception_handler) + + @app.get("/ok") + async def ok_route(): + return {"ok": True} + + @app.get("/explode") + async def explode_route(): + raise RuntimeError("TEST008 forced failure") + + return app + + +def test_t1008_success_request_has_request_id_header(): + client = TestClient(_build_test_app()) + + response = client.get("/ok") + + assert response.status_code == 200 + assert response.json() == {"ok": True} + assert "X-Request-ID" in response.headers + assert response.headers["X-Request-ID"] + + +def test_t1008_error_response_has_consistent_shape_and_status_code(): + client = TestClient(_build_test_app(), raise_server_exceptions=False) + + response = client.get("/explode") + + assert response.status_code == 500 + payload = response.json() + assert payload["message"] == "Internal server error" + assert "request_id" in payload + assert payload["request_id"] + assert response.headers["X-Request-ID"] == payload["request_id"]