mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
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:
76
e2e/README.md
Normal file
76
e2e/README.md
Normal 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
23
e2e/docker-compose.yml
Normal 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
6
e2e/init/01-schemas.sql
Normal 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
33
e2e/prepare.sh
Executable 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
63
e2e/run.sh
Executable 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"
|
||||
Reference in New Issue
Block a user