Skip to content

QA plan: FastAPI skeleton and /healthz (#12)

Story: FastAPI skeleton, /healthz, provider abstraction (#12)
Capability: Retrieval & generation API (#11)
Spec: specs/retrieval.md

TypeCoverage
UnitGET /healthz returns {"status": "ok", "db": true} when the DB repository returns healthy
UnitGET /healthz returns {"status": "ok", "db": false} when the DB is unreachable (repository stub raises)
UnitProvider protocol: EmbeddingProvider and LLMProvider protocol stubs satisfy the interface
UnitProviders are instantiated lazily; app starts and serves /healthz with no Bedrock credentials configured
IntegrationFastAPI app via TestClient: /healthz returns 200 against Docker Postgres
ContractOpenAPI spec: /healthz response schema in committed openapi.yaml matches the FastAPI annotation (RAG005 CI diff check)
Security/healthz is excluded from the Lambda authorizer route binding (RAG009); confirmed by route configuration test
  • Story #10 (schema) merged
  • FakeEmbeddingProvider, FakeLLMProvider in tests/stubs.py
  • GET /healthz unit tests pass for healthy and DB-down cases
  • TestClient integration test: 200 + correct JSON body against live Docker Postgres
  • Provider protocol tests pass
  • Lazy instantiation test: app imports without Bedrock credentials; /healthz returns 200
  • OpenAPI diff CI job passes (no committed spec drift)
  • Coverage: api/app.py, api/routes/health.py, providers/base.py each exceed 70%

Unit tests: in-process with TestClient and stubs. Integration: Docker Postgres. No AWS access.

pytest, FastAPI TestClient, pytest-cov. Newman runs the openapi-check job (RAG005).

  • app pytest fixture: FastAPI app instance with stubs wired via dependency injection
  • postgres_db fixture from Story #10
def test_healthz_db_down(app_with_failing_db):
# Given the DB repository is configured to raise on ping
client = TestClient(app_with_failing_db)
# When GET /healthz is called
response = client.get("/healthz")
# Then status is 200 and db field is false
assert response.status_code == 200
assert response.json() == {"status": "ok", "db": False}
  • Lazy instantiation test may pass even if providers are module-level if imports are cached. Use a subprocess or importlib.reload to isolate the import-time check.
  • OpenAPI spec drift: developers forget to regenerate openapi.yaml after annotation changes. RAG005 CI job catches this but the friction is the developer remembering to run the regen script locally first.

Spec: GET /healthz returns 200 with {"status": "ok", "db": true} when Postgres is reachable. Directly tested by the integration exit criterion above.