Files
edr-platform/e2e/freight/cypress/e2e/flows/general_mixed_reopen.cy.ts
2026-07-23 11:06:10 +00:00

137 lines
4.4 KiB
TypeScript

/**
* GENERAL mixed reopen (D+26) — the same two GENERAL contracts (one
* container, one bulk) book AGAIN after their first bookings die: cycle 1
* reserves a container + a bulk drawdown, nobody pays, both expire, the
* window reopens — and the SAME contracts issue fresh drawdowns in cycle 2
* that pay and allocate typed. GENERAL contracts survive dead bookings and
* dead cycles alike.
*
* Sequential steps of one journey — retries off.
*/
import {
bookBulk,
bookContainers,
clearGeneralBooking,
closeBookingWindow,
completeDocReview,
createImportSchedule,
departureAt,
eatDayStr,
endPaymentPhase,
ensureCorridorRoute,
expectWagonType,
forceReservationExpiry,
forceWindowOpen,
markPaid,
pollAllocations,
pollBookingStatus,
pollDb,
resetCorridorDay,
seedImportContract,
withBooking,
withSchedule,
type ScheduleRow,
} from "./import-utils";
const DEPARTURE = departureAt(26);
const BOOKING_DAY = eatDayStr(DEPARTURE);
const stamp = String(Date.now());
const stampedRef = (suffix: string) => `CTR-IMP-${stamp}-${suffix}`;
describe("GENERAL mixed reopen: the same contracts book again after a dead cycle", { retries: 0 }, () => {
before(() => {
cy.task("db:seedFile", "seed-import-corridor.sql");
seedImportContract({ suffix: "GMC", reference: stampedRef("GMC"), kind: "GENERAL" });
seedImportContract({
suffix: "GMB",
reference: stampedRef("GMB"),
kind: "GENERAL",
freight: "BULK",
});
});
it("operations prepares the corridor train — first window opens (cycle 1)", () => {
ensureCorridorRoute();
resetCorridorDay(DEPARTURE);
createImportSchedule({ departure: DEPARTURE, locoPair: ["LOCO-IMP-19", "LOCO-IMP-20"] });
withSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 45));
withSchedule(DEPARTURE, (s) => expect(s.booking_cycle_no, "cycle 1").to.eq(1));
});
it("cycle 1: a container and a bulk GENERAL drawdown clear per booking and are reserved", () => {
bookContainers({
suffix: "GMC",
runStamp: stamp,
isoSeed: 19_000,
twenty: 12, // 6 wagons
scheduledDate: BOOKING_DAY,
});
clearGeneralBooking("GMC", BOOKING_DAY);
bookBulk({ suffix: "GMB", tons: 700, scheduledDate: BOOKING_DAY }); // 10 wagons
clearGeneralBooking("GMB", BOOKING_DAY);
withSchedule(DEPARTURE, (s) => {
closeBookingWindow(s.id);
completeDocReview(s.id);
});
["GMC", "GMB"].forEach((suffix) =>
pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
);
});
it("nobody pays — both drawdowns expire and the window reopens", () => {
["GMC", "GMB"].forEach((suffix) => forceReservationExpiry(suffix));
withSchedule(DEPARTURE, (s) => {
endPaymentPhase(s.id);
// Same-tick guard-loop can chain PRE_WINDOW straight into OPEN when the
// reopen instant falls inside office hours — assert it left PAYMENT
// without concluding FULL/DONE, whichever phase it lands on.
pollDb<ScheduleRow>(
"window concludes not-full (PRE_WINDOW or fast-forwarded to OPEN)",
`SELECT window_phase FROM freight.train_schedules WHERE id = $1`,
[s.id],
(row) => !!row && row.window_phase !== "PAYMENT" && row.window_phase !== "DONE",
);
});
});
it("cycle 2: the SAME contracts issue fresh drawdowns that pay and allocate typed", () => {
withSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 45));
withSchedule(DEPARTURE, (s) => expect(s.booking_cycle_no, "cycle 2").to.eq(2));
bookContainers({
suffix: "GMC",
runStamp: stamp,
isoSeed: 19_200,
twenty: 12,
scheduledDate: BOOKING_DAY,
});
clearGeneralBooking("GMC", BOOKING_DAY);
bookBulk({ suffix: "GMB", tons: 700, scheduledDate: BOOKING_DAY });
clearGeneralBooking("GMB", BOOKING_DAY);
withSchedule(DEPARTURE, (s) => {
closeBookingWindow(s.id);
completeDocReview(s.id);
});
["GMC", "GMB"].forEach((suffix) =>
pollBookingStatus(suffix, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]),
);
markPaid("GMC");
pollAllocations("GMC", 6);
expectWagonType("GMC", "NW5", 6);
markPaid("GMB");
pollAllocations("GMB", 10);
expectWagonType("GMB", "CW4", 10);
// The cycle-1 corpses stay dead; both contracts remain ACTIVE.
["GMC", "GMB"].forEach((suffix) =>
withBooking(suffix, (b) => expect(b.status, `${suffix} cycle-2 rides`).to.eq("PAID")),
);
});
});
export {};