mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Adding all the tests and fixes to the passengers app
This commit is contained in:
154
e2e-ui/README.md
Normal file
154
e2e-ui/README.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# EDR Passenger — Playwright UI E2E
|
||||
|
||||
Browser E2E for the passenger platform. **Track A** = portal booking combinations; **Track B** =
|
||||
backoffice config → portal propagation. Scenario matrix: `docs/ui-e2e-test-matrix.md`.
|
||||
|
||||
## Status
|
||||
|
||||
**Track A + Track B implemented and green** — 27 passing specs, 1 documented skip (UA-7). Full suite
|
||||
runs deterministically in ~1.8 min (`workers:1`, one seeded DB shared serially). The whole booking
|
||||
flow is factored into `fixtures/booking-flow.ts` — `bookTrip(page, opts)` drives an arbitrary
|
||||
passenger mix, nationality, trip type, promo, and payment method end to end (search → select →
|
||||
passengers → seats → review → pay → confirmation), capturing the price at each hop; `bookOneAdult` is
|
||||
a thin back-compat wrapper.
|
||||
|
||||
### Coverage vs `docs/ui-e2e-test-matrix.md`
|
||||
|
||||
**Track A — booking combinations** (`specs/portal`, `specs/guest`):
|
||||
|
||||
| ID | Spec | What it proves |
|
||||
|----|------|----------------|
|
||||
| UA-1 | `ua1` | one-way 1A ETB WALLET — full money chain equal, CONFIRMED |
|
||||
| UA-1b | `ua1b-usd-divergence` | 🔴 USD card display fare diverges 100× from internal `baseFareMinor` |
|
||||
| UA-2 | `ua2-usd-booking` | 🔴 USD booking — reviewed total (ETB) vs stored total (USD) diverge 100× |
|
||||
| UA-3 | `ua3-djf` | DJF booking settles via forged gateway payment |
|
||||
| UA-3w | `ua3-djf` | 🔴 DJF WALLET — reviewed (DJF) vs stored (ETB) diverge |
|
||||
| UA-4 | `ua4-child-free` | first child <5 free → total = one adult fare, free child not seated |
|
||||
| UA-5 | `ua5-second-child-paid` | 1A+2C → second child pays full fare (2 seats) |
|
||||
| UA-6 | `ua6-round-trip` | 🔴 round-trip inbound leg has availability but no priced coach (booking blocked) |
|
||||
| UA-8 | `ua8-promo-drop` | ✅ valid promo now applied server-side; booking stored at the discounted total (H-13 fixed & guarded) |
|
||||
| UA-11 | `ua11-expired-promo` | expired promo ignored → full fare booked |
|
||||
| UA-13 | `ua13-forged-total` | ✅ client-forged `reviewedTotalMinor=1` now REJECTED 4xx, nothing stored (C-1 fixed & guarded) |
|
||||
| UA-14 | `ua14-forged-seat-fare` | ✅ guest forged `seatFareMinor=0` now REJECTED 4xx, nothing stored (C-1 fixed & guarded) |
|
||||
| UA-15 | `ua15-telebirr-shortpay` | ✅ short-paid gateway settlement now REFUSED — booking stays unconfirmed (C-4 fixed & guarded) |
|
||||
| UA-16 | `ua16-family-mix` | 2A+3C → two children free, one paid (3 seats) |
|
||||
|
||||
**Track B — config → portal propagation & validation gaps** (`specs/backoffice`, `specs/propagation`):
|
||||
|
||||
| ID | Spec | What it proves |
|
||||
|----|------|----------------|
|
||||
| PB-1 | `pb-config-propagation` | FX-rate change propagates live to portal USD pricing |
|
||||
| PB-2 / PB-2b | `pb-config-propagation` | seat-class base-price change propagates live; `basePrice` field drives the fare |
|
||||
| PB-4 | `pb-config-propagation` | station added in backoffice appears in the portal station list |
|
||||
| PB-7 | `config-validation` | 🔴 promo created with backoffice UI field names is inert (field-name mismatch) |
|
||||
| PB-10 | `pb-config-propagation` | ✅ deleting an FX rate now FAILS CLOSED (no priced fare) instead of a silent 1.0 collapse (M-5/H-2 fixed & guarded) |
|
||||
| BC-7 | `pb-config-propagation` | ✅ negative seat-class base price now REJECTED (400, DTO `@Min(0)`) (M-1 fixed & guarded) |
|
||||
| BC-8 | `config-validation` | ✅ promo over 100% now REJECTED (400, DTO `@Max(100)`) (M-2 fixed & guarded) |
|
||||
| BC-9 | `config-validation` | ✅ negative seat-hold duration now REJECTED (400, whitelisted typed `/config` DTO) (M-3 fixed & guarded) |
|
||||
| BC-10 | `config-validation` | 🔴 schedule with a past departure accepted (M-4) |
|
||||
| BC-11 | `pb-config-propagation` | ✅ non-admin passenger now FORBIDDEN (403) from FX writes; admin still allowed (C-8 fixed & guarded) |
|
||||
|
||||
### Deferred (documented, not silently omitted)
|
||||
|
||||
- **UA-7** (round-trip berth) — `specs/portal/ua7-berth.spec.ts` is `test.skip` with the reason: needs
|
||||
a bed CoachType + reverse-leg pricing, both backend prerequisites (see UA-6 for the reverse-leg gap).
|
||||
- **UA-9 / UA-10 / UA-12 / UA-17** — the matrix moves these to the API-level harness (over-100% /
|
||||
over-subtotal promos, loyalty over-redeem, DJF×promo negative total): no reachable browser path
|
||||
(server clamps `reviewedTotalMinor` ≥ 0; the portal never calls the loyalty/fare-quote path).
|
||||
- **PB-3/5/6/8/9, BC-1…BC-6** — additional config surfaces and delete-referenced/mid-flight/staleness
|
||||
variations of the finding classes already covered above; the matrix marks several as deferrable.
|
||||
|
||||
**Gateway settlement:** the real telebirr gateway is unreachable in the test env (`/payments/initiate`
|
||||
502s), so gateway rows (UA-3, UA-15) create the booking through the real browser flow and then inject
|
||||
settlement via `POST /internal/payments/mark-paid` — exactly the matrix's settlement-injection plan.
|
||||
|
||||
Portal testids used: `result-select-btn`, `coach-option`, `continue-passenger-details`,
|
||||
`pay-method-{TYPE}`. Everything else (passengers form + DOB modal, seats auto-assign, review, payment)
|
||||
is driven via name/placeholder/role selectors — no further source edits were needed.
|
||||
|
||||
**Seed note:** `Passenger.id` is set EQUAL to the IAM user id — see the comment in `seed-ui.ts`
|
||||
(`UI_IDS.passenger`) and the SUSPECTED FINDING below. Each coach seeds 48 seats so a full serial run
|
||||
never exhausts availability across specs.
|
||||
|
||||
## Suspected finding (surfaced while building UA-1)
|
||||
|
||||
`POST /bookings` (authenticated) overrides `passengerId` with the JWT user id
|
||||
(`bookings.controller.ts:528-532`, "never trust the request body"). The service only resolves an
|
||||
iamUserId → Passenger when it is **non-UUID** (`bookings.service.ts:773`). IAM user ids are UUIDs, so
|
||||
the resolver never fires and `booking.create` uses the iamUserId directly as `passengerId` → FK
|
||||
violation unless `Passenger.id == iamUserId`. This is why the seed aligns them. **Verify against a
|
||||
real IAM-authenticated booking** — if `req.user.id` is genuinely the iamUserId in production,
|
||||
authenticated portal bookings may be broken (guest path unaffected). Candidate for `docs/ISSUES.md`.
|
||||
|
||||
## Prerequisites — the running stack
|
||||
|
||||
The suite drives a live stack. `global-setup.ts` seeds + mints auth, but assumes the apps are
|
||||
already up. Bring them up once (leave running across test runs):
|
||||
|
||||
```bash
|
||||
# 1. Infra: test Postgres (5544) + RabbitMQ (5672, payment vhost)
|
||||
bash e2e/prepare.sh # postgres + migrations
|
||||
docker compose -f e2e/docker-compose.yml up -d rabbitmq-e2e
|
||||
|
||||
# 2. Build the shared types package (nest build needs the dist)
|
||||
pnpm --filter @edr/types build
|
||||
|
||||
# 3. passenger-api on :4000 against the 5544 DB, with org+staff seeding on
|
||||
# (apps/edr-passenger-api/.env sets DATABASE_URL=…5544, PORT=4000,
|
||||
# RABBITMQ_ENABLED=false, FAYDA_ENABLED=false, SEED_EDR_PASSENGER_ORG=true,
|
||||
# SEED_PASSENGER_STAFF=true, DEFAULT_PASSWORD=Test@1234)
|
||||
( cd apps/edr-passenger-api && pnpm dev ) # background
|
||||
|
||||
# 4. Web apps (each has .env.local → NEXT_PUBLIC_API_URL=http://localhost:4000)
|
||||
( cd apps/edr-passenger-web/portal && pnpm dev ) # :5174, background
|
||||
( cd apps/edr-passenger-web/backoffice && pnpm dev ) # :5184, background
|
||||
```
|
||||
|
||||
> `playwright.config.ts` now declares a `webServer` block that auto-boots api/portal/backoffice and
|
||||
> **reuses** them if already running, so steps 3–4 are optional in local dev. Gateway rows settle via
|
||||
> the internal `mark-paid` endpoint, so `apps/edr-payment-api` (:3003) is **not** required.
|
||||
|
||||
## Run
|
||||
|
||||
One command (infra → build → boot → seed+auth → run → open report):
|
||||
|
||||
```bash
|
||||
bash e2e-ui/run.sh # all projects; args pass through to playwright
|
||||
bash e2e-ui/run.sh --headed # watch it in a real browser
|
||||
SLOWMO=500 bash e2e-ui/run.sh --headed # slow every action by 500ms
|
||||
bash e2e-ui/run.sh --project=portal ua4 # one project / filter by title
|
||||
```
|
||||
|
||||
Or, against an already-running stack:
|
||||
|
||||
```bash
|
||||
pnpm test:e2e:ui # all projects
|
||||
pnpm test:e2e:ui -- --project=guest --project=backoffice # smoke only
|
||||
```
|
||||
|
||||
HTML report → `e2e-ui-report/index.html`.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
e2e-ui/
|
||||
playwright.config.ts projects: portal (passenger auth), guest (none),
|
||||
backoffice (staff auth), propagation (cross-app)
|
||||
global-setup.ts seeds test DB (seed-ui.ts) + mints staff.json via real /login
|
||||
fixtures/
|
||||
data.ts station IDs, sample depart date, results deep-link helper
|
||||
storage/staff.json generated staff storageState (gitignored)
|
||||
specs/{guest,portal,backoffice,propagation}/*.spec.ts
|
||||
```
|
||||
|
||||
Seed lives with the API harness: `apps/edr-passenger-api/test/fixtures/seed-ui.ts` (extends
|
||||
`seed-core.ts` with a bookable Train/Schedule/Coach/Seats, enabled PaymentMethods WALLET+TELEBIRR,
|
||||
promos, funded wallet). Run standalone: `npx ts-node test/fixtures/seed-ui.ts`.
|
||||
|
||||
## Auth model (grounded in the app)
|
||||
|
||||
- **Portal (passenger)**: `localStorage.auth_token` only, no server gate. (passenger storageState is
|
||||
a Phase 3 item — smoke uses the `guest` project.)
|
||||
- **Backoffice (staff)**: middleware requires the `auth_token` **cookie**; API guards check the
|
||||
session's permissions. `global-setup` logs in as the seeded `passenger.admin@edr.local` through
|
||||
the real `/login` UI and snapshots both. No hand-crafted tokens.
|
||||
Reference in New Issue
Block a user