/** * Reference pricing suite — proves the slim harness boots and exercises FareEngineService directly. * Also confirms matrix findings against the running engine: * D1 — a promo with percentOff > 100 drives the total NEGATIVE (no clamp at 0). * C1 ✅ FIXED (was 🔴 "missing USD→ETB FX rate is silently substituted with 1.0, fares * collapse ~100x"): CurrencyService.getExchangeRate() now fails closed on a missing * rate (see the "H-2: fail closed" comment in currency.service.ts, and * pricing-currency.e2e-spec.ts's C2/C2b) — FareEngineService.calculate() calls it * internally, so a missing rate now correctly rejects the fare calculation instead of * silently underpricing it. Updated below to assert the current, correct behavior. */ 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 ✅ a missing USD→ETB rate now rejects the fare calculation instead of silently pricing at parity", async () => { const withRate = await fareEngine.calculate(baseDto() as any); expect(withRate.totalMinor).toBeGreaterThan(0); // Remove the USD→ETB rate the seat-class formula multiplies by. await harness.prisma.currencyExchangeRate.deleteMany({ where: { fromCurrency: "USD", toCurrency: "ETB" }, }); await expect(fareEngine.calculate(baseDto() as any)).rejects.toThrow(/No exchange rate configured/i); }); });