test: add EDR passenger pricing/config E2E bug-hunt harness

Hermetic E2E harness targeting pricing integrity and backoffice config:
- e2e/ docker Postgres (5544) + prepare.sh/run.sh one-command runner + HTML report
- 6 suites / 23 tests reproducing pricing, FX, wallet, refund, config and auth
  defects (see docs/ISSUES.md); docs/e2e-test-matrix.md documents the matrix
- two-tier harness (slim module boot + direct service instantiation) to work
  around the IAM/RabbitMQ/file-type boot wall
- .env.test.example tracked; loader falls back to it for fresh checkouts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Muluhabt
2026-07-20 16:22:40 +03:00
parent 5d94e8acf2
commit c4f54a666b
24 changed files with 1938 additions and 2 deletions

76
e2e/README.md Normal file
View File

@@ -0,0 +1,76 @@
# EDR Passenger — Pricing/Config E2E Harness
Hermetic, bug-hunting test harness for the passenger platform. Targets **pricing integrity** and
**backoffice configuration**. Never touches a real database.
## Quick start
```bash
# 1. Bring up the isolated test Postgres (port 5544) and apply all migrations
bash e2e/prepare.sh
# (or: pnpm --filter @edr/passenger-api test:e2e:prepare)
# 2. Run the suites
pnpm --filter @edr/passenger-api test:e2e
# 3. Tear down
pnpm --filter @edr/passenger-api test:e2e:db:down
```
## What's isolated
- `e2e/docker-compose.yml` — Postgres 17 on host port **5544**, container `edr-passenger-e2e-db`,
`tmpfs` data (wiped on `down`). Distinct from any dev/prod DB. Schemas `passenger`, `iam`,
`edr_payment` created by `e2e/init/01-schemas.sql`.
- `apps/edr-passenger-api/.env.test` — points every connection at 5544; brokers/IAM/Fayda OFF.
Loaded by `test/setup/load-env.ts` before the app boots.
## Architecture — why two tiers
The full `AppModule` cannot be booted in-process under jest:
- `@tria-plc/api-common` (pulled via IAM) `require("file-type")`, which is ESM-only → jest's
CommonJS resolver fails. (Worked around with a `moduleNameMapper` stub, but…)
- `@golevelup/nestjs-rabbitmq` + microservice RMQ clients + `onApplicationBootstrap` seeders hang
the boot waiting on a broker that isn't there.
So tests use one of two tiers:
**Tier 1 — slim module harness** (`test/setup/slim-app.ts`). Boots ONLY the pricing/config domain
modules that are free of the IAM/RabbitMQ chain: `fare-engine, currency, currencies, promos,
seat-classes, stations, schedules, segments, system-config`. Two entry points:
- `createServiceHarness()` — resolve services (e.g. `FareEngineService`) for direct method calls.
- `createHttpHarness()` — full HTTP app with the SAME `ValidationPipe` as `src/main.ts`, for
controller/DTO/pipe (client-trust, validation) tests over supertest.
**Tier 2 — direct instantiation** (`test/setup/prisma.ts`). For services behind the wall
(`BookingsService, PaymentsService, WalletService, LoyaltyService, ExcessBaggageService`):
`new TheService(getTestPrisma(), ...mockedCollaborators)` and assert the money logic. Avoids booting
the module graph entirely.
## Fixtures
`test/fixtures/seed-core.ts` — deterministic graph (coach type → LOCAL/INTERNATIONAL seat classes →
3 stations → route with distance-bearing stops → FX rates) with fixed UUIDs in `IDS`. Call
`resetAndSeedCore(prisma)` in `beforeEach`. The repo's `prisma/seed.ts` is disabled (all steps
commented out) and is intentionally NOT used.
## Suites (see `docs/e2e-test-matrix.md` for the full matrix)
Spec files are `test/*.e2e-spec.ts`. Each is tagged with the matrix IDs it covers. 🔴 in a test name
marks a confirmed defect the test documents/reproduces (the assertion encodes the BUGGY behavior;
a passing 🔴 test = the bug is present).
Current suites (all green):
- `pricing-fare-engine.e2e-spec.ts` — baseline + D1/D2/D4 (promo → negative total), C1 (FX fallback)
- `pricing-currency.e2e-spec.ts` — C2/C2b (display↔charge FX divergence), C3 (future rate), C5 (unit divergence)
- `money-integrity.e2e-spec.ts` — F1/F2 (free wallet top-up), G4/G5 (refund never disbursed), E1/E2 (baggage)
- `config-validation.e2e-spec.ts` — H1/H2 (negative fares), H4/H5 (promo bounds/date)
- `auth-gaps.e2e-spec.ts` — J1 (unauthenticated FX writes)
- `critical-repro.e2e-spec.ts` — C-1 (client-controlled booking total), C-4 (payment amount never
validated), C-6 (wallet double-spend via a deterministic race barrier)
`test/app.e2e-spec.ts` is a pre-existing repo test that boots the FULL AppModule; it is excluded via
`testPathIgnorePatterns` because that boot hangs in-process (RabbitMQ connect + ESM `file-type`) — a
harness limitation documented above, not a product bug.
Findings are catalogued in `docs/ISSUES.md`.

23
e2e/docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
# Hermetic test database for the EDR passenger E2E harness.
# Isolated from any dev/prod Postgres: distinct container name + non-standard host port (5544).
# Single database `edr_database` with schemas `passenger`, `iam`, `edr_payment` (see init/01-schemas.sql).
services:
postgres-e2e:
image: postgres:17
container_name: edr-passenger-e2e-db
environment:
POSTGRES_USER: edr
POSTGRES_PASSWORD: edr_secret
POSTGRES_DB: edr_database
ports:
- "5544:5432"
volumes:
- ./init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U edr -d edr_database"]
interval: 3s
timeout: 3s
retries: 20
tmpfs:
# Ephemeral storage — every `docker compose down` wipes the DB. Nothing to clean up.
- /var/lib/postgresql/data

6
e2e/init/01-schemas.sql Normal file
View File

@@ -0,0 +1,6 @@
-- Runs once on first container start (Postgres initdb hook).
-- Prisma migrate (passenger) and TypeORM migrate (iam) create their own tables,
-- but the schemas must exist first. edr_payment is owned by the payment-api.
CREATE SCHEMA IF NOT EXISTS passenger;
CREATE SCHEMA IF NOT EXISTS iam;
CREATE SCHEMA IF NOT EXISTS edr_payment;

33
e2e/prepare.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Bring up the hermetic test DB and apply all migrations. Idempotent — safe to re-run.
# Usage: bash e2e/prepare.sh (from repo root or anywhere)
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
API="$HERE/../apps/edr-passenger-api"
export DATABASE_URL="postgresql://edr:edr_secret@localhost:5544/edr_database?schema=passenger"
export DATABASE_HOST=localhost DATABASE_PORT=5544 DATABASE_NAME=edr_database
export DATABASE_USER=edr DATABASE_PASSWORD=edr_secret DATABASE_SCHEMA=iam
echo "==> Starting test Postgres (port 5544)"
docker compose -f "$HERE/docker-compose.yml" up -d
echo "==> Waiting for healthy"
for i in $(seq 1 30); do
status="$(docker inspect --format '{{.State.Health.Status}}' edr-passenger-e2e-db 2>/dev/null || echo none)"
[ "$status" = "healthy" ] && break
sleep 2
done
[ "${status:-}" = "healthy" ] || { echo "DB did not become healthy"; exit 1; }
echo "==> Prisma migrate deploy (passenger schema)"
( cd "$API" && npx prisma migrate deploy )
echo "==> IAM TypeORM migrations (iam schema)"
( cd "$API" && node scripts/run-iam-migrations.cjs )
echo "==> Prisma client generate"
( cd "$API" && npx prisma generate >/dev/null )
echo "==> Ready. Run: pnpm --filter @edr/passenger-api test:e2e"

63
e2e/run.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# One-shot E2E: ensure Docker is up → start the test DB + migrations → run all suites → open the
# HTML dashboard. Safe to re-run. The DB is left running for fast subsequent runs unless --down.
#
# bash e2e/run.sh # run everything, leave the DB up, open the report
# bash e2e/run.sh --down # same, but tear the DB down afterwards
# bash e2e/run.sh --no-open # don't auto-open the browser (just print the path)
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
API="$HERE/../apps/edr-passenger-api"
REPORT="$API/e2e-report/index.html"
DOWN=0; OPEN=1
for arg in "$@"; do
case "$arg" in
--down) DOWN=1 ;;
--no-open) OPEN=0 ;;
*) echo "unknown flag: $arg" >&2; exit 2 ;;
esac
done
# 1. Ensure the Docker daemon is running (start Docker Desktop on macOS if needed).
if ! docker info >/dev/null 2>&1; then
echo "==> Docker daemon not running; attempting to start Docker Desktop…"
open -a Docker 2>/dev/null || { echo "Could not launch Docker. Start it manually and re-run."; exit 1; }
printf " waiting for Docker"
for _ in $(seq 1 40); do
if docker info >/dev/null 2>&1; then echo " — up"; break; fi
printf "."; sleep 2
done
docker info >/dev/null 2>&1 || { echo; echo "Docker did not start in time."; exit 1; }
fi
# 2. Bring up the test DB + apply migrations (idempotent).
bash "$HERE/prepare.sh"
# 3. Run all suites (this also writes the HTML report via the jest-html-reporters config).
# Don't let a test failure abort the script — we still want to open the report.
set +e
( cd "$API" && npx jest --config ./test/jest-e2e.json )
JEST_EXIT=$?
set -e
# 4. Open (or print) the report.
if [ -f "$REPORT" ]; then
if [ "$OPEN" -eq 1 ]; then
echo "==> Opening report: $REPORT"
open "$REPORT" 2>/dev/null || echo " (open it manually: $REPORT)"
else
echo "==> Report written: $REPORT"
fi
else
echo "!! No report generated (tests may have failed to run)."
fi
# 5. Optional teardown.
if [ "$DOWN" -eq 1 ]; then
echo "==> Tearing down the test DB"
docker compose -f "$HERE/docker-compose.yml" down
fi
exit "$JEST_EXIT"