Files
edr-platform/integration/src/bulk-import-waiting-expiry.it.ts
2026-08-03 10:51:43 +00:00

126 lines
4.0 KiB
TypeScript

/**
* BULK IMPORT — the CW4 train fills from THREE wheat bookings; three more sit
* in the waiting pool of the same window. The selected trio pays through the
* gateway and allocates; when the cycle concludes FULL the waiting three
* expire with the day — they never ride and never pay.
*
* 70 T per CW4 wagon, 54-wagon consist:
* selected: BWA 1 400 T = 20 w, BWB 1 400 T = 20 w, BWC 980 T = 14 w → Σ 54
* waiting: BW1 / BW2 / BW3 700 T = 10 w each
*/
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { closeDb, gateway } from "./client";
import {
allocatedWagons,
bookBulkReady,
bookingRow,
closeBookingWindow,
completeDocReview,
createSchedule,
departureAt,
eatDayStr,
endPaymentPhase,
extendPayWindow,
ensureCorridorRoute,
forceWindowOpen,
payViaGateway,
pollAllocations,
pollBookingStatus,
pollWindow,
releaseUnpaidHolds,
resetCorridorDay,
seedTenantContracts,
setPriority,
} from "./flows";
const DEPARTURE = departureAt(21);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const STAMP = String(Date.now());
const SELECTED = [
{ suffix: "BWA", tons: 1400, wagons: 20 },
{ suffix: "BWB", tons: 1400, wagons: 20 },
{ suffix: "BWC", tons: 980, wagons: 14 },
];
const WAITING = [
{ suffix: "BW1", tons: 700, wagons: 10 },
{ suffix: "BW2", tons: 700, wagons: 10 },
{ suffix: "BW3", tons: 700, wagons: 10 },
];
const ALL = [...SELECTED, ...WAITING];
describe("bulk import: three bookings fill the train, three wait and expire", () => {
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,
ALL.map((b) => ({ suffix: b.suffix, freight: "BULK" as const })),
);
scheduleId = (
await createSchedule({
departure: DEPARTURE,
kind: "bulk",
locoPair: ["LOCO-IMP-17", "LOCO-IMP-18"],
})
).id;
await forceWindowOpen(scheduleId, 45);
for (const [i, b] of ALL.entries()) {
const id = await bookBulkReady({
contractId: contracts.get(b.suffix)!,
tons: b.tons,
scheduledDate: BOOKING_DAY,
});
booking.set(b.suffix, id);
// Priority order = the order above: the exact-fill trio picks first.
await setPriority(id, i + 1);
}
}, 1_800_000);
afterAll(closeDb);
it("the batch selects exactly the three that fill 54 wagons; the rest keep waiting", async () => {
await closeBookingWindow(scheduleId);
await completeDocReview(scheduleId);
for (const b of SELECTED) {
await pollBookingStatus(booking.get(b.suffix)!, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]);
}
for (const b of WAITING) {
const row = await bookingRow(booking.get(b.suffix)!);
expect(row.status, `${b.suffix} still waiting`).toBe("FULLY_EXECUTED");
expect(row.payment_deadline, `${b.suffix} has no pay deadline`).toBeNull();
}
});
it("the three selected pay through the gateway and allocate — 54/54", async () => {
await extendPayWindow(scheduleId, SELECTED.map((b) => booking.get(b.suffix)!));
for (const b of SELECTED) {
await payViaGateway(booking.get(b.suffix)!);
await pollAllocations(booking.get(b.suffix)!, b.wagons);
}
expect(await allocatedWagons(scheduleId), "54 wagons allocated").toBe(54);
});
it("the cycle concludes FULL — the three waiting bookings expire with the day", async () => {
await endPaymentPhase(scheduleId);
await pollWindow(
scheduleId,
(s) => s.booking_window_status === "FULL" && s.window_phase === "DONE",
"FULL + DONE",
);
for (const b of WAITING) await pollBookingStatus(booking.get(b.suffix)!, "EXPIRED", 40);
for (const b of SELECTED) {
const row = await bookingRow(booking.get(b.suffix)!);
expect(row.status, `${b.suffix} stays PAID`).toBe("PAID");
}
});
});