managarten/docker-compose.test.yml
Till JS 5af4ddab3c test(integration): end-to-end auth flow test with Mailpit + CI gating
Adds a 13-step integration test that exercises register → email
verification → login → JWT validation → /me/data → encryption-vault
init/key → logout against a real stack of postgres + redis + mailpit +
mana-auth + mana-notify in docker compose.

Verified locally that this catches every regression we hit on
2026-04-08 in well under a second:

  - missing nanoid dependency → register endpoint 500
  - missing MANA_AUTH_KEK env passthrough → mana-auth never starts
  - missing encryption-vault SQL migrations → vault endpoints 500
  - wrong cookie name in /api/v1/auth/login → no accessToken in response
  - mana-notify SMTP misconfigured → mailpit poll times out

Files:

- docker-compose.test.yml — minimal isolated stack on alt ports
  (postgres 5443, redis 6390, mailpit 1026/8026, mana-auth 3091,
  mana-notify 3092). Runs alongside the dev stack without collision.
  Postgres healthcheck runs a real query rather than just pg_isready
  to avoid the race where pg_isready reports healthy while the docker
  init scripts are still running on a unix socket.

- tests/integration/auth-flow.test.ts — bun test that drives the full
  flow via fetch + mailpit's REST API. Cleans up its test user from
  postgres in afterAll. Self-contained, no extra deps.

- tests/integration/README.md — what's covered, why it exists, how
  to run locally + extend.

- scripts/run-integration-tests.sh — orchestrator. Brings up the
  stack, pushes the @mana/auth Drizzle schema, applies the
  encryption-vault SQL migrations (002, 003), restarts mana-auth so
  it sees the fresh tables, runs the test, tears down on exit.
  KEEP_STACK=1 to leave it up for manual mailpit inspection.

- docker-compose.dev.yml — also adds Mailpit as a regular dev service
  (ports 1025/8025) so local development can have a working email
  capture without spinning up the test stack.

- .github/workflows/ci.yml — new auth-integration job that runs on
  every PR. Calls run-integration-tests.sh; on failure dumps
  mana-auth + mana-notify logs and the mailpit message queue. Marked
  as a required check via the existing PR validation pipeline.

Reproduced 3 clean runs and 1 negative-control run (removed nanoid
from package.json → mana-auth container exits → script aborts with
non-zero) before committing. Full happy path runs in ~22s on a warm
Docker cache.
2026-04-08 17:14:02 +02:00

166 lines
5.6 KiB
YAML

# Integration test stack for the auth/credentials/encryption-vault flow.
#
# Spins up the minimum stack needed to register, verify, log in, and
# exercise the encryption-vault: postgres + redis + mailpit (fake SMTP)
# + mana-auth + mana-notify. No mana-credits, mana-sync, mana-media etc.
# — those are not on the auth-flow critical path and would just slow
# down the build.
#
# Ports are offset from docker-compose.dev.yml so this stack can run
# alongside a normal dev environment. Everything is bound to 127.0.0.1
# so it's only reachable from the same machine.
#
# Usage:
# ./scripts/run-integration-tests.sh
#
# Or manually:
# docker compose -f docker-compose.test.yml up -d --build
# docker compose -f docker-compose.test.yml down -v
#
# The compose project is namespaced as `mana-test` so the containers,
# network and volumes don't collide with the dev stack.
name: mana-test
services:
postgres:
image: postgres:16-alpine
container_name: mana-test-postgres
environment:
POSTGRES_DB: mana_platform
POSTGRES_USER: mana
POSTGRES_PASSWORD: testpassword
volumes:
- ./docker/init-db:/docker-entrypoint-initdb.d:ro
ports:
- "127.0.0.1:5443:5432"
networks:
- mana-test
healthcheck:
# pg_isready alone reports healthy while the docker-entrypoint init
# scripts are still running on a unix socket — TCP connections from
# other containers then race-fail with "connection refused". Run a
# real query against the actual platform DB so we only flip healthy
# once postgres is genuinely accepting external TCP traffic.
test: ["CMD-SHELL", "PGPASSWORD=testpassword psql -h localhost -U mana -d mana_platform -tAc 'SELECT 1' >/dev/null"]
interval: 2s
timeout: 3s
retries: 30
start_period: 5s
redis:
image: redis:7-alpine
container_name: mana-test-redis
command: redis-server --requirepass testpassword --maxmemory 64mb
ports:
- "127.0.0.1:6390:6379"
networks:
- mana-test
healthcheck:
test: ["CMD", "redis-cli", "-a", "testpassword", "ping"]
interval: 2s
timeout: 3s
retries: 10
# Fake SMTP server. Captures every outbound email and exposes them
# via a REST API on :8025 (also a web UI on the same port). Tests
# poll the API to find the verification email.
mailpit:
image: axllent/mailpit:latest
container_name: mana-test-mailpit
environment:
MP_SMTP_AUTH_ACCEPT_ANY: "1"
MP_SMTP_AUTH_ALLOW_INSECURE: "1"
ports:
- "127.0.0.1:1026:1025" # SMTP
- "127.0.0.1:8026:8025" # HTTP API + Web UI
networks:
- mana-test
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025/api/v1/info"]
interval: 2s
timeout: 3s
retries: 10
mana-auth:
build:
context: ./services/mana-auth
dockerfile: Dockerfile
container_name: mana-test-mana-auth
environment:
NODE_ENV: production # exercise the prod KEK validation path
PORT: 3001
DATABASE_URL: postgresql://mana:testpassword@postgres:5432/mana_platform
# BASE_URL must be reachable from INSIDE the container — the validate
# endpoint fetches its own JWKS via this URL, and the JWT iss claim
# uses it. The test rewrites email verify URLs from `mana-auth:3001`
# to the host-bound port before following them.
BASE_URL: http://mana-auth:3001
COOKIE_DOMAIN: localhost
BETTER_AUTH_SECRET: test-secret-not-for-production
MANA_AUTH_KEK: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # 32 zero bytes, test only
MANA_NOTIFY_URL: http://mana-notify:3013
MANA_SERVICE_KEY: test-service-key
MANA_CREDITS_URL: http://localhost:9 # unreachable, .catch() swallows it
MANA_SUBSCRIPTIONS_URL: http://localhost:9
CORS_ORIGINS: http://localhost:5173,http://localhost:3091
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:3091:3001"
networks:
- mana-test
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 3s
timeout: 3s
retries: 20
start_period: 10s
mana-notify:
build:
context: .
dockerfile: services/mana-notify/Dockerfile
container_name: mana-test-mana-notify
# mana-notify pings the database once at startup and exits on
# failure. If postgres is mid-restart at exactly that millisecond
# we want compose to bring it back up rather than declare the
# whole stack dead.
restart: on-failure:5
environment:
PORT: 3013
DATABASE_URL: postgresql://mana:testpassword@postgres:5432/mana_platform?sslmode=disable
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: testpassword
SERVICE_KEY: test-service-key
MANA_AUTH_URL: http://mana-auth:3001
SMTP_HOST: mailpit
SMTP_PORT: 1025
SMTP_USER: test
SMTP_PASSWORD: test
SMTP_FROM: "Mana Test <noreply@test.local>"
SMTP_INSECURE_TLS: "true"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
mailpit:
condition: service_healthy
ports:
- "127.0.0.1:3092:3013"
networks:
- mana-test
healthcheck:
# Override the Dockerfile's port-3040 healthcheck — mana-notify
# actually binds to the PORT env var (3013 here).
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3013/health"]
interval: 3s
timeout: 3s
retries: 20
start_period: 5s
networks:
mana-test:
driver: bridge