Files
agatha 602648ef56 Feat: Gate API docs endpoints behind API_DOCS_ENABLED env var
When API_DOCS_ENABLED=false, FastAPI registers no routes for /docs,
/redoc, or /openapi.json, returning 404 for all three. Default is true
for backwards compatibility. Invalid values fall back to true (FR-007).

Fix: Remove tests/ and alembic/ from api/.dockerignore so the test
Dockerfile (which uses COPY . .) includes the test suite; Dockerfile.prod
is unaffected as it only copies app/ explicitly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 20:40:48 +00:00

6.8 KiB

Tasks: API Documentation Visibility Gate

Input: Design documents from specs/012-api-docs-gate/ Prerequisites: plan.md , spec.md , research.md , contracts/docs-endpoints.md , quickstart.md

Tests: TDD is non-negotiable (§5.1). Failing tests are written before implementation code in each phase.

Organization: No setup or foundational phases — this feature modifies three existing files and adds one new test file. Phase 3 (US1) covers the disable path; Phase 4 (US2) verifies the enable/default path using the same implementation; Phase 5 polishes.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel with other [P] tasks in the same phase
  • [Story]: Which user story this task belongs to
  • Exact file paths included in every task description

Phase 3: User Story 1 — Documentation Hidden in Production (Priority: P1) 🎯 MVP

Goal: When API_DOCS_ENABLED=false, all three documentation endpoints (/docs, /redoc, /openapi.json) return 404. All other endpoints are unaffected.

Independent Test: make test-unit passes the new settings tests; make test-integration passes the new test_docs_disabled integration test.

Tests for User Story 1 (TDD — write first, confirm failure before T003)

  • T001 [US1] Add three failing unit tests to api/tests/unit/test_config.py using the existing _apply_env/_BASE_ENV pattern:

    1. test_api_docs_enabled_default — call Settings() with _BASE_ENV only (no API_DOCS_ENABLED); assert s.api_docs_enabled is True
    2. test_api_docs_enabled_false — call Settings() with _BASE_ENV + {"API_DOCS_ENABLED": "false"}; assert s.api_docs_enabled is False
    3. test_api_docs_invalid_value_defaults_to_enabled — call Settings() with _BASE_ENV + {"API_DOCS_ENABLED": "not-a-bool"}; assert s.api_docs_enabled is True (graceful fallback, FR-007) All three tests fail before T003 because api_docs_enabled does not yet exist on Settings.
  • T002 [US1] Create api/tests/integration/test_docs_gate.py with two failing integration tests; the file MUST set up a minimal app client using from starlette.testclient import TestClient and the importlib.reload + get_settings.cache_clear() pattern shown in plan.md:

    1. test_docs_hidden_when_flag_disabled(monkeypatch) — set API_DOCS_ENABLED=false via monkeypatch + all required env vars (DATABASE_URL, JWT_SECRET_KEY, OWNER_USERNAME, OWNER_PASSWORD, S3_ENDPOINT_URL, S3_BUCKET_NAME, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY); call get_settings.cache_clear(); importlib.reload(app.main); create TestClient(app.main.app); assert /docs → 404, /redoc → 404, /openapi.json → 404, /api/v1/health → 200; after test, call get_settings.cache_clear() again as cleanup
    2. test_docs_visible_when_flag_enabled(monkeypatch) — same setup but with API_DOCS_ENABLED=true (or omit it); assert /docs → 200, /redoc → 200, /openapi.json → 200 Both tests fail before T003/T004 because api_docs_enabled does not exist on Settings.

Implementation for User Story 1

  • T003 [US1] Add api_docs_enabled: bool = True field and a coerce_docs_enabled field validator to the Settings class in api/app/config.py: the validator MUST use mode='before', be a @classmethod, and wrap Pydantic bool coercion in a try/except that returns True on any exception (implements FR-007); import field_validator from pydantic at the top of the file; the field goes after the existing login_trusted_proxy_ips field.

  • T004 [US1] Update api/app/main.py: before the app = FastAPI(...) call, add _settings = get_settings(); add docs_url="/docs" if _settings.api_docs_enabled else None, redoc_url="/redoc" if _settings.api_docs_enabled else None, and openapi_url="/openapi.json" if _settings.api_docs_enabled else None as keyword arguments to the FastAPI() constructor; the existing module-level defaults for app.state (after the app = FastAPI(...) line) are unchanged.

  • T005 [US1] Verify TDD green for US1: run cd api && python -m pytest tests/unit/ -v -k "docs" and confirm all three new unit tests pass; then run cd api && python -m pytest tests/unit/ -v to confirm no regressions in the full 102-test unit suite.

Checkpoint: US1 is complete. With API_DOCS_ENABLED=false the three docs endpoints return 404; all other endpoints are unaffected.


Phase 4: User Story 2 — Documentation Available in Development (Priority: P2)

Goal: Without the flag set (or with it set to true), docs endpoints behave identically to before this feature. Default is backwards compatible.

Independent Test: make test-integration — the test_docs_visible_when_flag_enabled test written in T002 passes, confirming the enabled/default path.

  • T006 [US2] Verify TDD green for US2: run make test-integration from /workspace and confirm all integration tests pass, including test_docs_gate.py::test_docs_visible_when_flag_enabled and the full existing suite (102 tests + 2 new = 104 total).

Checkpoint: Both user stories verified. Flag disabled → 404; flag enabled or absent → unchanged behaviour.


Phase 5: Polish & Cross-Cutting Concerns

  • T007 Add documentation for API_DOCS_ENABLED to /workspace/.env.example: insert a new section after the LOGIN_TRUSTED_PROXY_IPS block with a comment and API_DOCS_ENABLED=true; the comment MUST note that this should be set to false in production to avoid publicly exposing the API schema

  • T008 Run ruff check api/app/config.py api/app/main.py api/tests/integration/test_docs_gate.py from /workspace/api and fix any lint violations; then run ruff check api/ to confirm the full API directory is clean


Dependencies & Execution Order

  • T001 and T002 can run in parallel (different files, both TDD-red before implementation)
  • T003 must complete before T004 (main.py reads from config.py)
  • T005 after T003 and T004
  • T006 after T005
  • T007 and T008 can run in parallel (different files, after all tests pass)

Execution Order Summary

Step 1: T001 ∥ T002   (write failing tests — TDD red)
Step 2: T003          (implement config.py — turns T001 green)
Step 3: T004          (implement main.py — turns T002 green)
Step 4: T005          (verify unit tests green)
Step 5: T006          (verify integration tests green — regression gate)
Step 6: T007 ∥ T008   (polish — .env.example + ruff)

Implementation Strategy

MVP (US1 + US2 — one implementation covers both)

  1. Write failing tests (T001, T002)
  2. Add api_docs_enabled to config.py (T003)
  3. Update FastAPI() constructor in main.py (T004)
  4. Verify all tests green (T005, T006)
  5. Polish (T007, T008)

US1 and US2 share the same implementation — the flag controls both paths. There is no separate implementation for US2; the default value of true is the entire implementation of US2.