Files
reactbin/specs/008-postgres-integration-tests/tasks.md
agatha f3e0021ee8 Feat: Enforce PostgreSQL for integration tests; add Docker test stack
- conftest.py: pytest_configure guard rejects non-postgresql+asyncpg:// URLs
  before any test collects (per constitution §2.5/§5.2 v1.3.0)
- docker-compose.test.yml: isolated postgres-test (5433) + minio-test (9002)
  + api-test runner; one command runs the full suite against real PostgreSQL
- Makefile: test-unit and test-integration targets
- .env.test.example: documents variables needed to run tests outside Docker
- Fix pre-existing test bug: integration tests using client fixture (NoOpAuthProvider)
  for write operations (upload/delete/patch) now use authed_client with Bearer
  token — these were never caught because tests never ran against a live stack

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:14:12 +00:00

7.2 KiB
Raw Blame History

Tasks: PostgreSQL Integration Test Infrastructure

Input: Design documents from specs/008-postgres-integration-tests/ Prerequisites: plan.md , spec.md , research.md , quickstart.md

Tests: TDD is non-negotiable (§5.1). For infrastructure tasks the "failing test" is a verification step that confirms the thing being built is absent before building it, then confirms it works after. Every user story has an explicit TDD red step before its implementation task.

Organization: No foundational blocking phase — all three user stories touch independent files and can proceed in order.

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 1: Setup

No new project structure required. The existing layout accommodates all changes.


Phase 2: User Story 1 — Dialect guard in conftest (Priority: P1) 🎯 MVP

Goal: pytest api/tests/integration/ exits immediately with a clear message if the database URL is not postgresql+asyncpg://.

Independent Test: Run TEST_DATABASE_URL=sqlite+aiosqlite:///test.db python -m pytest api/tests/integration/ -q — command exits in < 2 s with the error message Integration tests require postgresql+asyncpg:// and no tests are collected.

  • T001 [US1] Confirm guard is absent (TDD red): from api/, run TEST_DATABASE_URL=sqlite+aiosqlite:///test.db python -m pytest tests/integration/ -q --co 2>&1 | head -20 — observe that tests ARE collected and note the count (guard not yet in place)
  • T002 [US1] Add pytest_configure hook to api/tests/integration/conftest.py — resolve URL via os.getenv("TEST_DATABASE_URL") or os.getenv("DATABASE_URL", ""), call pytest.exit("Integration tests require postgresql+asyncpg://...", returncode=1) if URL does not start with postgresql+asyncpg://; place hook before any imports that depend on the database URL
  • T003 [US1] Verify guard works (TDD green): run TEST_DATABASE_URL=sqlite+aiosqlite:///test.db python -m pytest api/tests/integration/ -q — confirm immediate exit with the correct error message and zero tests collected; also confirm a valid postgresql+asyncpg:// URL does not trigger the guard

Checkpoint: Dialect-mismatched test runs are blocked before any test collects.


Phase 3: User Story 2 — Docker Compose test stack (Priority: P1)

Goal: docker compose -f docker-compose.test.yml run --rm api-test runs the full integration suite against isolated PostgreSQL and MinIO services on different ports than the dev stack.

Independent Test: Run docker compose -f docker-compose.test.yml run --rm api-test from the repo root — all tests pass; verify docker compose ps shows dev services (if running) are unaffected on their original ports.

  • T004 [US2] Confirm compose file is absent (TDD red): run test -f docker-compose.test.yml && echo EXISTS || echo ABSENT — confirm output is ABSENT
  • T005 [US2] Create docker-compose.test.yml at the repo root with four services: postgres-test (image postgres:16-alpine, host port 5433, db reactbin_test), minio-test (image minio/minio:latest, host ports 9002/9003), minio-init-test (creates bucket reactbin-test, depends on minio-test healthy), and api-test (builds from ./api, runs python -m pytest tests/ -v, depends on postgres-test healthy and minio-init-test completed, environment sets TEST_DATABASE_URL=postgresql+asyncpg://reactbin:reactbin@postgres-test:5432/reactbin_test, DATABASE_URL to same value, and all S3 vars pointing to minio-test:9000 with bucket reactbin-test) — follow exact design in specs/008-postgres-integration-tests/plan.md
  • T006 [US2] Verify compose stack (TDD green): run docker compose -f docker-compose.test.yml run --rm api-test — confirm all integration tests pass; confirm no errors about missing env vars or connection failures

Checkpoint: Full integration suite runs against real PostgreSQL via one command.


Phase 4: User Story 3 — Test documentation (Priority: P2)

Goal: .env.test.example and Makefile document how to run both test tiers.

Independent Test: Read .env.test.example — all variables needed for integration tests are present with example values. Run make test-unit — pytest unit suite runs without Docker and passes.

  • T007 [P] [US3] Create .env.test.example at the repo root documenting all variables required to run integration tests outside Docker: TEST_DATABASE_URL, DATABASE_URL, S3_ENDPOINT_URL, S3_BUCKET_NAME, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_REGION, JWT_SECRET_KEY, OWNER_USERNAME, OWNER_PASSWORD, API_BASE_URL, MAX_UPLOAD_BYTES — with example values pointing to localhost:5433 and localhost:9002 (test service ports); include a comment explaining how to start test services first — follow exact design in specs/008-postgres-integration-tests/plan.md
  • T008 [P] [US3] Create Makefile at the repo root with .PHONY: test-unit test-integration, test-unit target running cd api && python -m pytest tests/unit/ -v, and test-integration target running docker compose -f docker-compose.test.yml run --rm api-test
  • T009 [US3] Verify make test-unit — unit tests pass without Docker (validates the Makefile target and confirms unit tests have no Docker dependency)
  • T010 Verify make test-integration — Docker integration suite passes end-to-end (cross-story verification: exercises the US2 compose stack via the US3 Makefile target)

Checkpoint: All three user stories independently functional.


Phase 5: Polish & Cross-Cutting Concerns

  • T011 Run ruff check api/app/ api/tests/ — zero violations (conftest change must pass ruff; fix any issues)

Dependencies & Execution Order

Phase Dependencies

  • Phase 2 (US1): No external dependencies — can start immediately
  • Phase 3 (US2): Depends on Phase 2 (guard must be in place so the compose stack run exercises it)
  • Phase 4 (US3): T007 and T008 are independent file writes (can run in parallel with each other after Phase 3); T009 requires T008; T010 requires T008 and T006
  • Phase 5 (Polish): Depends on all prior phases

Within Phase 4

  • T007 ∥ T008 (different files, no dependency)
  • T009 after T008 (Makefile must exist)
  • T010 after T008 and T006 (requires both Makefile and compose stack)

Execution Order Summary

Step 1: T001, T002, T003 (sequential — TDD for guard)
Step 2: T004, T005, T006 (sequential — TDD for compose stack)
Step 3 (parallel): T007, T008
Step 4: T009 (after T008), T010 (after T008 + T006)
Step 5: T011

Implementation Strategy

MVP (US1 — the guard)

  1. Complete T001T003
  2. Validate: SQLite URL is blocked; PostgreSQL URL proceeds
  3. US2 and US3 add the infrastructure to make the mandate practical

Incremental Delivery

  • After Phase 2: Dialect bugs are caught immediately — core safety net is in place
  • After Phase 3: Full integration suite runs against PostgreSQL via one Docker command
  • After Phase 4: Both test tiers are documented and accessible via make
  • After Phase 5: Lint clean, ready for merge