Files
edr-platform/integration/src/g1-s2-exact-fill.it.ts
2026-08-03 10:51:43 +00:00

143 lines
4.7 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.

/**
* GROUP 1 · S2 — four bookings pay and fill the train to the slot.
*
* A 3×40ft = 3 wagons · 3 containers
* B 20×20ft + 10×40ft = 20 wagons · 30 containers
* C 25×40ft = 25 wagons · 25 containers
* D 10×20ft = 5 wagons · 10 containers
* ─────────
* 53/53 → FULL
*
* Everyone is selected, everyone pays, nothing splits and nobody waits. What
* this really guards is the ALLOCATION rather than the arithmetic: the 53
* wagons carry 68 containers and every one must land on exactly one slot with
* its number on it. A booking that took wagons but never mapped its units would
* still read 53/53 on the board — hence the per-container assertion at the end.
*/
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { closeDb, gateway } from "./client";
import {
G1_WAGONS,
allocatedWagons,
bookContainersReady,
bookingRow,
closeBookingWindow,
completeDocReview,
containerWagons,
createBuiltTrainSchedule,
departureAt,
eatDayStr,
endPaymentPhase,
ensureCorridorRoute,
expectContainersPlaced,
expectNoPartialOffer,
extendPayWindow,
forceWindowOpen,
linkedBookings,
payViaGateway,
pollAllocations,
pollBookingStatus,
pollWindow,
releaseUnpaidHolds,
resetCorridorDay,
seedTenantContracts,
} from "./flows";
const DEPARTURE = departureAt(51);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const STAMP = String(Date.now());
const SHAPES = {
A: { twenty: 0, forty: 3, wagons: 3, containers: 3 },
B: { twenty: 20, forty: 10, wagons: 20, containers: 30 },
C: { twenty: 0, forty: 25, wagons: 25, containers: 25 },
D: { twenty: 10, forty: 0, wagons: 5, containers: 10 },
} as const;
const ORDER = ["A", "B", "C", "D"] as const;
const TOTAL_CONTAINERS = ORDER.reduce((sum, s) => sum + SHAPES[s].containers, 0); // 68
describe("g1 s2: four bookings pay and fill the train exactly", () => {
const booking = new Map<string, string>();
let scheduleId: string;
beforeAll(async () => {
await gateway.reset();
await releaseUnpaidHolds();
await ensureCorridorRoute();
await resetCorridorDay(DEPARTURE);
const contracts = await seedTenantContracts(
STAMP,
ORDER.map((suffix) => ({ suffix, freight: "CONTAINER" as const })),
);
scheduleId = (await createBuiltTrainSchedule({ departure: DEPARTURE })).id;
await forceWindowOpen(scheduleId, 60);
let isoSeed = 21_000;
for (const suffix of ORDER) {
const shape = SHAPES[suffix];
booking.set(
suffix,
await bookContainersReady({
contractId: contracts.get(suffix)!,
runStamp: STAMP,
isoSeed,
twenty: shape.twenty,
forty: shape.forty,
scheduledDate: BOOKING_DAY,
}),
);
isoSeed += shape.containers;
}
}, 1_800_000);
afterAll(closeDb);
it("the four bookings add up to exactly one trainload", () => {
for (const suffix of ORDER) {
expect(containerWagons(SHAPES[suffix].twenty, SHAPES[suffix].forty), `${suffix} wagons`).toBe(
SHAPES[suffix].wagons,
);
}
expect(
ORDER.reduce((sum, s) => sum + SHAPES[s].wagons, 0),
"A+B+C+D fill the train exactly",
).toBe(G1_WAGONS);
});
it("the batch reserves all four whole — an exact fit offers nobody a split", async () => {
await closeBookingWindow(scheduleId);
await completeDocReview(scheduleId);
for (const suffix of ORDER) {
await pollBookingStatus(booking.get(suffix)!, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]);
await expectNoPartialOffer(booking.get(suffix)!, suffix);
}
});
it("all four pay through the gateway and are allocated onto the train", async () => {
await extendPayWindow(scheduleId, [...booking.values()]);
for (const suffix of ORDER) {
await payViaGateway(booking.get(suffix)!);
await pollAllocations(booking.get(suffix)!, SHAPES[suffix].wagons);
}
});
it("the train is FULL at 53/53 and every one of the 68 containers has a slot", async () => {
await endPaymentPhase(scheduleId);
await pollWindow(
scheduleId,
(s) => s.booking_window_status === "FULL" && s.window_phase === "DONE",
"FULL + DONE",
);
expect(await allocatedWagons(scheduleId), "53 wagons allocated").toBe(G1_WAGONS);
expect(await linkedBookings(scheduleId), "4 bookings linked").toBe(4);
for (const suffix of ORDER) {
expect((await bookingRow(booking.get(suffix)!)).status, `${suffix} rides`).toBe("PAID");
}
// Allocation is per CONTAINER, not just per wagon: 53 filled slots with
// only some units mapped would still read as a full train.
await expectContainersPlaced(scheduleId, TOTAL_CONTAINERS);
});
});