from __future__ import annotations import uuid import httpx import pytest from fastapi import APIRouter from app.core.problems import ApiProblem from app.main import create_app @pytest.mark.asyncio async def test_application_factory_generates_openapi_without_runtime_secrets() -> None: app = create_app() schema = app.openapi() assert schema["openapi"].startswith("3.") assert "/api/v1/health/live" in schema["paths"] assert "/api/v1/meta" in schema["paths"] assert "/health/live" not in schema["paths"] @pytest.mark.asyncio async def test_trace_id_accepts_only_uuid_and_is_returned() -> None: app = create_app() transport = httpx.ASGITransport(app=app) supplied = str(uuid.uuid4()) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: accepted = await client.get("/api/v1/health/live", headers={"x-request-id": supplied}) replaced = await client.get( "/api/v1/health/live", headers={"x-request-id": "secret-or-unbounded-client-value"}, ) assert accepted.headers["x-request-id"] == supplied assert uuid.UUID(replaced.headers["x-request-id"]) assert replaced.headers["x-request-id"] != "secret-or-unbounded-client-value" @pytest.mark.asyncio async def test_formal_api_problem_is_sanitized_and_traceable() -> None: app = create_app() router = APIRouter() @router.get("/api/v1/problem-test") async def fail() -> None: raise ApiProblem( status=409, code="VERSION_CONFLICT", title="Version conflict", detail="The resource changed; reload and retry.", ) app.include_router(router) transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/problem-test") payload = response.json() assert response.status_code == 409 assert response.headers["content-type"].startswith("application/problem+json") assert payload["code"] == "VERSION_CONFLICT" assert uuid.UUID(payload["trace_id"]) assert payload["field_errors"] == []