Files
pl_mail/compose.test.yaml
T
2026-07-31 08:37:13 +02:00

138 lines
6.0 KiB
YAML

# Local test stack — PHPUnit and the Playwright E2E suite.
#
# npm run test:unit:docker
# npm run test:e2e:docker
#
# Deliberately its OWN compose project (see `name:` below) with its OWN Postgres,
# so it can never reconcile, recreate or restart the dev stack, and can never
# reach the database holding real mail. `docker compose ...` without -f is
# untouched by this file.
#
# Served by the app's own FrankenPHP image rather than PHP's built-in server:
# variables_order is GPCS, so `php -S` never exposes APP_ENV to Symfony and the
# app silently falls back to .env.local and the dev database. A server SAPI puts
# the environment into $_SERVER, so the values below actually take effect.
name: pl_mail_test
x-test-app: &test-app
# The dev target, not the ghcr image: prod is built with `composer install
# --no-dev` (no PHPUnit) and ships a dump-env'd .env.local.php pinning prod.
build:
context: .
target: frankenphp_dev
volumes:
- ./:/app
# A dedicated var volume. Never share the dev stack's `app_var`: it is a
# named volume overlaying the bind mount, so sharing it would let the two
# environments write each other's caches and built assets.
- app_var_test:/app/var
# Same trick, same reason, for the compiled assets `asset-map:compile`
# writes in app-init. `public/assets/` does not exist in the repo and the
# dev stack bind-mounts this very directory, so without an overlay the
# compiled output would land on the host and be served — stale — by the
# dev stack on every source edit, since Caddy prefers a real file over
# index.php. Keeping it in a volume means the test stack gets static assets
# and development is untouched by it.
#
# Docker does create an *empty* `public/assets/` on the host as the mount
# point. That is harmless and expected: the compiled files live in the
# volume, so the dev stack finds nothing there and falls through to PHP
# exactly as it did before. `.gitignore` already covers the directory.
- app_public_assets_test:/app/public/assets
environment: &test-env
APP_ENV: test
APP_DEBUG: "1"
# Pinned, and passed as real environment variables rather than left to
# .env.test: the entrypoint generates these when the environment does not
# carry them, and a generated value would win over the dotenv one. The
# suite wants them fixed — stable CSRF tokens, and a seed encrypted with
# the same key every run. Same values as .env.test.
APP_SECRET: 282065e0f71bf96c0dbcf0496c1f5d3e
APP_ENCRYPTION_KEY: emL6R8zs0pSXCVV2HvVvbFubriWJIla7kgnqSGqCJ8E=
# Host is `database`, unlike .env.test which targets 127.0.0.1 for CI.
# Doctrine's when@test dbname_suffix (config/packages/doctrine.yaml) turns
# this into `app_test` automatically.
DATABASE_URL: postgresql://app:app@database:5432/app?serverVersion=18&charset=utf8
MESSENGER_TRANSPORT_DSN: in-memory://
MAILER_DSN: null://null
APP_DEV_USER_EMAIL: e2e@plmail.test
APP_DEV_USER_PASSWORD: e2e-password-change-me
# Same-origin so a missing hub fails fast instead of raising a cross-origin
# TLS error against https://localhost. There is no Mercure hub here; the
# suite does not assert on live updates.
MERCURE_PUBLIC_URL: http://127.0.0.1:${TEST_HTTP_PORT:-8001}/.well-known/mercure
depends_on:
database:
condition: service_healthy
services:
database:
image: postgres:18-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
healthcheck:
test: ["CMD", "pg_isready", "-d", "app", "-U", "app"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- database_data_test:/var/lib/postgresql
# Owns schema, assets and the seed user, then exits. `app` waits for it.
app-init:
<<: *test-app
# The image entrypoint waits on the database and runs migrations *before*
# the database exists, so it cannot bootstrap an empty volume. Drop to the
# upstream PHP entrypoint and do the ordering here.
entrypoint: ["docker-php-entrypoint"]
restart: "no"
command:
- sh
- -c
- |
set -e
php bin/console doctrine:database:create --if-not-exists
php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
php bin/console importmap:install
# Not optional: specs assert toBeVisible()/toBeHidden(), which depend on
# Tailwind's `.hidden { display: none }`. Building here also keeps this
# stack independent of the dev `tailwind` watcher.
php bin/console tailwind:build
# Compiled, not served through PHP. Without this, `try_files {path}
# index.php` in frankenphp/Caddyfile sends every asset request into a
# full Symfony kernel boot: /login alone pulls 54 modulepreload entries
# plus 2 stylesheets, measured at ~35ms each against ~1.6ms for a real
# static file. Playwright gives every test a cold HTTP cache, so that
# was ~1.9s of PHP spent serving JavaScript once per test — about 40%
# of the whole e2e suite.
php bin/console asset-map:compile
php bin/console app:test:seed-user
# The admin the PHPUnit controller tests look for. Seeded here rather
# than left to the Playwright suite, which is where it used to come
# from: PageRendersTest and friends skip themselves when the user is
# missing, so `npm run test:unit:docker` on a fresh volume quietly ran
# ~50 fewer tests than it appeared to until the browser suite had been
# run at least once.
php bin/console app:test:seed-user --admin --email=e2e-admin@plmail.test --password=e2e-admin-password
app:
<<: *test-app
environment:
<<: *test-env
# Plain HTTP on 80 inside the container — no TLS, no cert prompts.
SERVER_NAME: ":80"
ports:
- "${TEST_HTTP_PORT:-8001}:80"
depends_on:
database:
condition: service_healthy
app-init:
condition: service_completed_successfully
volumes:
app_var_test:
app_public_assets_test:
database_data_test: