Files
edr-platform/apps/edr-passenger-api/test/pricing-fare-engine.e2e-spec.ts
Muluhabt c4f54a666b 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>
2026-07-20 16:22:40 +03:00

132 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Reference pricing suite — proves the slim harness boots and exercises FareEngineService directly.
* Also confirms two matrix findings against the running engine:
* D1 — a promo with percentOff > 100 drives the total NEGATIVE (no clamp at 0).
* C1 — a missing USD→ETB FX rate is silently substituted with 1.0 (fares collapse ~100x).
*/
import { FareEngineService } from "../src/modules/fare-engine/fare-engine.service";
import {
createServiceHarness,
ServiceHarness,
} from "./setup/slim-app";
import {
IDS,
resetAndSeedCore,
DISTANCE,
USD_TO_ETB,
} from "./fixtures/seed-core";
describe("Pricing — FareEngineService (slim harness)", () => {
let harness: ServiceHarness;
let fareEngine: FareEngineService;
beforeAll(async () => {
harness = await createServiceHarness();
fareEngine = harness.moduleRef.get(FareEngineService);
});
afterAll(async () => {
await harness?.close();
});
beforeEach(async () => {
await resetAndSeedCore(harness.prisma);
});
// nationality 'Ethiopian' → LOCAL seat class (3.00 ETB/km) and ETB billing (rate 1).
const baseDto = () => ({
routeId: IDS.route,
originStationId: IDS.stationA,
destinationStationId: IDS.stationB,
seatClassId: IDS.seatClassLocal,
nationality: "Ethiopian",
adultCount: 1,
childCount: 0,
});
it("boots and computes a positive baseline fare (A→B, local, 1 adult)", async () => {
const result = await fareEngine.calculate(baseDto() as any);
// 100km × 3.00 ETB/km × 1 × USD_TO_ETB(100) = 30000 minor (see seat-class formula).
expect(result.totalMinor).toBeGreaterThan(0);
expect(result.totalMinor).toBe(DISTANCE.B * 3 * USD_TO_ETB);
});
it("D1 🔴 promo percentOff=150 produces a NEGATIVE total (no floor at 0)", async () => {
await harness.prisma.promotion.create({
data: {
title: "Overshoot",
code: "OVER150",
percentOff: 150,
validUntil: new Date(Date.now() + 86_400_000),
active: true,
},
});
const result = await fareEngine.calculate({
...baseDto(),
promoCode: "OVER150",
} as any);
// Expected (correct) behavior: total clamped at >= 0. Actual: negative.
expect(result.totalMinor).toBeLessThan(0);
});
it("D2 🔴 fixed amountOffMinor larger than subtotal drives total NEGATIVE", async () => {
const base = await fareEngine.calculate(baseDto() as any); // 30000 minor
await harness.prisma.promotion.create({
data: {
title: "Huge fixed",
code: "FIXEDBIG",
amountOffMinor: base.totalMinor + 10_000,
validUntil: new Date(Date.now() + 86_400_000),
active: true,
},
});
const result = await fareEngine.calculate({
...baseDto(),
promoCode: "FIXEDBIG",
} as any);
expect(result.totalMinor).toBeLessThan(0);
});
it("D4 🔴 promo with percentOff=0 is treated as FIXED (0 is falsy) and applies amountOffMinor", async () => {
// A promo intended as '0% off' but also carrying a stray fixed amount: the falsy check
// `promo.percentOff ? percent : amountOffMinor` wrongly applies the fixed discount.
await harness.prisma.promotion.create({
data: {
title: "Zero percent",
code: "ZERO0",
percentOff: 0,
amountOffMinor: 5000,
validUntil: new Date(Date.now() + 86_400_000),
active: true,
},
});
const base = await fareEngine.calculate(baseDto() as any);
const withPromo = await fareEngine.calculate({
...baseDto(),
promoCode: "ZERO0",
} as any);
// A true 0% promo should not change the price; here it deducts the fixed 5000.
expect(withPromo.totalMinor).toBe(base.totalMinor - 5000);
});
it("C1 🔴 missing USD→ETB rate silently falls back to 1.0 (fare collapses ~100x)", async () => {
const withRate = await fareEngine.calculate(baseDto() as any);
// Remove the USD→ETB rate the seat-class formula multiplies by.
await harness.prisma.currencyExchangeRate.deleteMany({
where: { fromCurrency: "USD", toCurrency: "ETB" },
});
const withoutRate = await fareEngine.calculate(baseDto() as any);
// Correct behavior would be to reject/flag; instead the fare silently drops by the rate factor.
expect(withoutRate.totalMinor).toBe(withRate.totalMinor / USD_TO_ETB);
expect(withoutRate.totalMinor).toBeLessThan(withRate.totalMinor);
});
});