Files
reactbin/specs/008-postgres-integration-tests/research.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

3.0 KiB

Research: PostgreSQL Integration Test Infrastructure

Decision 1: How to enforce the PostgreSQL dialect in conftest.py

Decision: Add a pytest_configure hook (or a module-level guard in conftest.py) that calls pytest.exit() if the resolved database URL does not start with postgresql+asyncpg://.

Rationale: pytest_configure runs before collection, giving the clearest possible failure signal. A module-level assertion would also work but produces a less readable traceback. pytest.exit() with a human-readable message is the idiomatic approach.

Alternatives considered:

  • A custom pytest plugin in a separate file — unnecessary complexity for a one-liner guard.
  • Raising an exception in the engine fixture — runs too late (after collection); developers see confusing fixture errors instead of a clear message.

Decision 2: Separate docker-compose.test.yml vs profiles in docker-compose.yml

Decision: Use a standalone docker-compose.test.yml at the repo root.

Rationale: Docker Compose profiles require the developer to remember --profile test on every command. A separate file is explicit and self-contained. The test file can define its own service names and ports without touching the dev compose file at all.

Alternatives considered:

  • docker-compose.yml with a test profile — profile discovery is non-obvious; modifying the dev file risks breaking the dev stack.
  • A docker-compose.override.yml — override files apply automatically to docker compose up, which is the opposite of what we want for tests.

Decision 3: Port assignments for test services

Decision:

  • postgres-test: host port 5433 (standard offset from dev 5432)
  • minio-test API: host port 9002 (offset from dev 9000)
  • minio-test console: host port 9003 (offset from dev 9001)

Rationale: Predictable offsets make it easy to remember. Developers running both stacks simultaneously won't hit port conflicts.


Decision 4: S3 isolation strategy for tests

Decision: The api-test service sets S3_BUCKET_NAME=reactbin-test pointing to the dedicated minio-test instance. The minio-init-test sidecar creates that bucket before tests run.

Rationale: The existing conftest already manages database isolation via create_all / drop_all. MinIO requires bucket pre-creation (same as dev). A dedicated test bucket on a dedicated test MinIO instance gives full isolation. No changes to application storage code are needed.


Decision 5: Makefile vs shell scripts

Decision: A Makefile at the repo root with test-unit and test-integration targets.

Rationale: make is universally available on Linux/macOS developer machines. The targets are short wrappers that document the canonical test invocation. No build logic; just convenience aliases.

Alternatives considered:

  • Shell scripts (scripts/test.sh) — no discoverability; make help is more ergonomic.
  • package.json scripts — wrong tool for a Python/Docker project.
  • justfile — not universally installed.