/** * Auth/authorization gaps (matrix Suite J), via route guard metadata — no boot needed. * * C-8 🔴 The exchange-rate write routes (PUT upsert, PATCH update) carry no METHOD-LEVEL guard, so * they get only the global JwtGuard (authentication) and NOT @PassengerAdmin (authorization) * — unlike DELETE, which is admin-gated. Net effect (verified live in * e2e-ui .../pb-config-propagation.spec.ts BC-11): anonymous → 401, but ANY authenticated * user incl. a passenger → 200 rewrites live FX. fare-engine/currency.controller.ts:25,32,42 * * NOTE: this metadata check proves the missing ADMIN guard, NOT "unauthenticated" — a global * APP_GUARD=JwtGuard (SharedAuthModule) still requires a valid token. The earlier "unauthenticated * FX write" reading was a false positive corrected by the live BC-11 test. */ import "reflect-metadata"; import { CurrencyController } from "../src/modules/fare-engine/currency.controller"; const GUARDS_METADATA = "__guards__"; function guardsOn(handler: unknown): unknown[] { return (Reflect.getMetadata(GUARDS_METADATA, handler as object) as unknown[]) ?? []; } describe("Auth gaps (Suite J)", () => { it("C-8 🔴 PUT upsert exchange-rate has NO admin guard (only the global JwtGuard applies)", () => { expect(guardsOn(CurrencyController.prototype.upsert)).toHaveLength(0); }); it("C-8 🔴 PATCH update exchange-rate has NO admin guard (only the global JwtGuard applies)", () => { expect(guardsOn(CurrencyController.prototype.update)).toHaveLength(0); }); it("C-8 control: DELETE exchange-rate IS admin-gated — proving writes should be too", () => { expect(guardsOn(CurrencyController.prototype.remove).length).toBeGreaterThan(0); }); });