# The stack the end-to-end tests run against. # # Its own Postgres and Redis, on their own volumes, on their own network, with # their own ports. Nothing here touches the running site: the point of an # end-to-end test is to do the destructive things a real user can do — sit a # session, delete an article, sign out everywhere — and none of that may # happen to somebody's actual work. # # docker compose -f docker-compose.test.yml up -d --build # docker compose -f docker-compose.test.yml run --rm seed # cd e2e && npx playwright test # docker compose -f docker-compose.test.yml down -v # -v: take the data with it # # The database is thrown away with the stack. That is deliberate — a test suite # that depends on data surviving between runs is a test suite that passes on # your machine. name: pedshub-test services: postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_DB: pedquiz_test POSTGRES_USER: pedquiz_test POSTGRES_PASSWORD: pedquiz_test # No host port. Nothing outside this stack has any business reaching it, # and binding 5432 would collide with the real one on a developer's box. volumes: - test_postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U pedquiz_test"] interval: 3s timeout: 3s retries: 20 redis: image: redis:7-alpine command: redis-server --save "" --appendonly no healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 3s timeout: 3s retries: 20 backend: build: ./backend command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers --forwarded-allow-ips=* environment: &backend_env DATABASE_URL: postgresql://pedquiz_test:pedquiz_test@postgres:5432/pedquiz_test REDIS_URL: redis://redis:6379/0 # Fixed, so a token minted by the seed script is still valid in the # browser. Test-only by construction: it is in a file in the repository. SECRET_KEY: e2e-only-not-a-secret-e2e-only-not-a-secret ALGORITHM: HS256 ACCESS_TOKEN_EXPIRE_MINUTES: "1440" APP_URL: http://localhost:8095 # Nothing may leave the machine during a test. No model, no mail, no # object store: a suite that quietly calls a paid API is a suite nobody # can run twice. LITELLM_API_BASE: http://127.0.0.1:9/blackhole LITELLM_API_KEY: unused LITELLM_MODEL: none SMTP_HOST: "" ANONYMIZED_TELEMETRY: "False" LOG_LEVEL: WARNING # Registration open and no captcha, so the sign-up journey is testable. CAP_SECRET_KEY: "" # The whole suite arrives from one address, so the guess limiter would # stop the run rather than an attacker. Raised here and nowhere else. LOGIN_MAX_ATTEMPTS: "10000" REFRESH_MAX_PER_HOUR: "10000" volumes: - test_uploads:/app/uploads - test_chroma:/app/chroma_data depends_on: postgres: { condition: service_healthy } redis: { condition: service_started } healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request;urllib.request.urlopen('http://localhost:8000/api/health')\""] interval: 3s timeout: 5s retries: 30 # The same nginx image the site runs, so the tests exercise the real routing # and the real built bundle rather than a dev server. frontend: build: ./frontend ports: - "127.0.0.1:8095:80" depends_on: backend: { condition: service_healthy } # One-shot. Applies migrations and writes the fixture the tests expect. seed: build: ./backend environment: *backend_env volumes: - test_uploads:/app/uploads - ./e2e/seed.py:/app/seed.py:ro depends_on: postgres: { condition: service_healthy } entrypoint: ["python", "/app/seed.py"] profiles: ["tools"] volumes: test_postgres: test_uploads: test_chroma: