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:
128
apps/edr-passenger-api/test/fixtures/seed-core.ts
vendored
Normal file
128
apps/edr-passenger-api/test/fixtures/seed-core.ts
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Deterministic core fixtures for the pricing E2E suites.
|
||||
*
|
||||
* The repo's `prisma/seed.ts` is entirely commented out (every step disabled), so the harness
|
||||
* builds its own minimal, fully-controlled graph: coach type → seat classes → stations → route
|
||||
* with distance-bearing stops → FX rates. Fixed UUIDs let specs reference entities directly.
|
||||
*
|
||||
* Uses a bare PrismaClient (not the Nest PrismaService) so it can run in jest globalSetup or
|
||||
* inside a spec without booting the app. Reads DATABASE_URL from process.env (load-env sets it).
|
||||
*/
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
export const IDS = {
|
||||
coachType: "00000000-0000-4000-8000-000000000001",
|
||||
seatClassLocal: "00000000-0000-4000-8000-000000000010",
|
||||
seatClassIntl: "00000000-0000-4000-8000-000000000011",
|
||||
stationA: "00000000-0000-4000-8000-000000000020",
|
||||
stationB: "00000000-0000-4000-8000-000000000021",
|
||||
stationC: "00000000-0000-4000-8000-000000000022",
|
||||
route: "00000000-0000-4000-8000-000000000030",
|
||||
} as const;
|
||||
|
||||
/** Route stop distances (km from origin). A=0, B=100, C=250 → A→B is 100km, A→C is 250km. */
|
||||
export const DISTANCE = { A: 0, B: 100, C: 250 } as const;
|
||||
|
||||
/**
|
||||
* FX rate chosen so the seat-class distance formula (which multiplies an ETB/km rate by the
|
||||
* USD→ETB rate — see fare-engine.service.ts:157) yields whole ETB-minor amounts. 100 makes the
|
||||
* major→minor scaling line up; a realistic rate (e.g. 132) would visibly distort domestic fares,
|
||||
* which is itself a finding the suites probe.
|
||||
*/
|
||||
export const USD_TO_ETB = 100;
|
||||
export const ETB_TO_DJF = 1.8;
|
||||
|
||||
/** TRUNCATE every table in the `passenger` schema (except Prisma's migration bookkeeping). */
|
||||
export async function truncateAllPassenger(prisma: PrismaClient): Promise<void> {
|
||||
const rows = await prisma.$queryRawUnsafe<Array<{ tablename: string }>>(
|
||||
`SELECT tablename FROM pg_tables WHERE schemaname = 'passenger' AND tablename <> '_prisma_migrations'`,
|
||||
);
|
||||
if (rows.length === 0) return;
|
||||
const list = rows.map((r) => `passenger."${r.tablename}"`).join(", ");
|
||||
await prisma.$executeRawUnsafe(
|
||||
`TRUNCATE ${list} RESTART IDENTITY CASCADE`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Insert the deterministic core graph. Call after truncateAllPassenger. */
|
||||
export async function seedCore(prisma: PrismaClient): Promise<void> {
|
||||
const past = new Date("2020-01-01T00:00:00.000Z");
|
||||
|
||||
await prisma.coachType.create({
|
||||
data: {
|
||||
id: IDS.coachType,
|
||||
code: "STD",
|
||||
name: "Standard Coach",
|
||||
type: "passenger",
|
||||
},
|
||||
});
|
||||
|
||||
// LOCAL and INTERNATIONAL seat classes share the coach type + bedPosition (null = regular seat),
|
||||
// which is exactly how fare-engine picks the nationality-matched class (findFirst on those keys).
|
||||
await prisma.seatClass.createMany({
|
||||
data: [
|
||||
{
|
||||
id: IDS.seatClassLocal,
|
||||
coachTypeId: IDS.coachType,
|
||||
name: "Local Standard",
|
||||
nationalityType: "LOCAL",
|
||||
bedPosition: null,
|
||||
baseFareMinor: 300, // 3.00 ETB/km
|
||||
premiumMinor: 0,
|
||||
insuranceFeeMinor: 0,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: IDS.seatClassIntl,
|
||||
coachTypeId: IDS.coachType,
|
||||
name: "Intl Standard",
|
||||
nationalityType: "INTERNATIONAL",
|
||||
bedPosition: null,
|
||||
baseFareMinor: 500, // 5.00 ETB/km
|
||||
premiumMinor: 0,
|
||||
insuranceFeeMinor: 0,
|
||||
isActive: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.station.createMany({
|
||||
data: [
|
||||
{ id: IDS.stationA, code: "AAA", name: "Alpha", city: "Alpha City", sequence: 1 },
|
||||
{ id: IDS.stationB, code: "BBB", name: "Bravo", city: "Bravo City", sequence: 2 },
|
||||
{ id: IDS.stationC, code: "CCC", name: "Charlie", city: "Charlie City", sequence: 3 },
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.route.create({
|
||||
data: {
|
||||
id: IDS.route,
|
||||
code: "RT-MAIN",
|
||||
name: "Main Line",
|
||||
effectiveFrom: past,
|
||||
active: true,
|
||||
stops: {
|
||||
create: [
|
||||
{ stationId: IDS.stationA, sequence: 1, distanceKm: DISTANCE.A },
|
||||
{ stationId: IDS.stationB, sequence: 2, distanceKm: DISTANCE.B },
|
||||
{ stationId: IDS.stationC, sequence: 3, distanceKm: DISTANCE.C },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.currencyExchangeRate.createMany({
|
||||
data: [
|
||||
{ fromCurrency: "USD", toCurrency: "ETB", rate: USD_TO_ETB, effectiveDate: new Date() },
|
||||
{ fromCurrency: "ETB", toCurrency: "USD", rate: 1 / USD_TO_ETB, effectiveDate: new Date() },
|
||||
{ fromCurrency: "ETB", toCurrency: "DJF", rate: ETB_TO_DJF, effectiveDate: new Date() },
|
||||
{ fromCurrency: "DJF", toCurrency: "ETB", rate: 1 / ETB_TO_DJF, effectiveDate: new Date() },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/** Convenience: reset + seed in one call. */
|
||||
export async function resetAndSeedCore(prisma: PrismaClient): Promise<void> {
|
||||
await truncateAllPassenger(prisma);
|
||||
await seedCore(prisma);
|
||||
}
|
||||
Reference in New Issue
Block a user