/** * BULK IMPORT — nobody pays in the first cycle: both reserved wheat bookings * expire, the cycle concludes NOT-full, and the window REOPENS for a second * cycle on the same train. A fresh 700 T booking arrives in cycle 2, pays * through the gateway, and allocates. Import days reopen; they don't die. * * The expiry itself is only possible because the payment service is real here: * before expiring an unpaid hold the engine asks the gateway whether a late * payment landed, and defers forever on an unverifiable answer. */ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { closeDb, gateway } from "./client"; import { bookBulkReady, bookingRow, closeBookingWindow, completeDocReview, createSchedule, departureAt, eatDayStr, endPaymentPhase, ensureCorridorRoute, forceReservationExpiry, forceWindowOpen, payViaGateway, pollAllocations, pollBookingStatus, pollWindow, releaseUnpaidHolds, resetCorridorDay, scheduleRow, seedTenantContracts, } from "./flows"; const DEPARTURE = departureAt(22); const BOOKING_DAY = eatDayStr(DEPARTURE); const STAMP = String(Date.now()); const SUFFIXES = ["BRA", "BRB", "BRC"] as const; describe("bulk import: dead first cycle — expire all, reopen, book again", () => { const booking = new Map(); let contracts: Map; let scheduleId: string; beforeAll(async () => { await gateway.reset(); await releaseUnpaidHolds(); await ensureCorridorRoute(); await resetCorridorDay(DEPARTURE); contracts = await seedTenantContracts( STAMP, SUFFIXES.map((suffix) => ({ suffix, freight: "BULK" as const })), ); scheduleId = ( await createSchedule({ departure: DEPARTURE, kind: "bulk", locoPair: ["LOCO-IMP-23", "LOCO-IMP-24"], }) ).id; await forceWindowOpen(scheduleId, 45); expect((await scheduleRow(scheduleId)).booking_cycle_no, "cycle 1").toBe(1); for (const suffix of ["BRA", "BRB"] as const) { booking.set( suffix, await bookBulkReady({ contractId: contracts.get(suffix)!, tons: 1400, scheduledDate: BOOKING_DAY, }), ); } }, 1_800_000); afterAll(closeDb); it("two customers are reserved in cycle 1", async () => { await closeBookingWindow(scheduleId); await completeDocReview(scheduleId); for (const suffix of ["BRA", "BRB"] as const) { await pollBookingStatus(booking.get(suffix)!, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]); } }); it("nobody pays — both reservations expire and the cycle concludes not-full", async () => { for (const suffix of ["BRA", "BRB"] as const) { await forceReservationExpiry(booking.get(suffix)!); } await endPaymentPhase(scheduleId); // When the reopen instant lands inside office hours the 10s tick chains // PRE_WINDOW straight into OPEN, so PRE_WINDOW is not a reliably observable // resting state — assert only that the cycle left PAYMENT without DONE. await pollWindow( scheduleId, (s) => s.window_phase !== "PAYMENT" && s.window_phase !== "DONE", "concluded not-full", ); }); it("the second window opens (cycle 2) and a fresh 700 T booking pays and allocates", async () => { await forceWindowOpen(scheduleId, 45); expect((await scheduleRow(scheduleId)).booking_cycle_no, "cycle 2").toBe(2); const brc = await bookBulkReady({ contractId: contracts.get("BRC")!, tons: 700, scheduledDate: BOOKING_DAY, }); booking.set("BRC", brc); await closeBookingWindow(scheduleId); await completeDocReview(scheduleId); await pollBookingStatus(brc, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]); await payViaGateway(brc); await pollAllocations(brc, 10); for (const suffix of ["BRA", "BRB"] as const) { const row = await bookingRow(booking.get(suffix)!); expect(row.status, `${suffix} stays expired`).toBe("EXPIRED"); } expect((await bookingRow(brc)).train_schedule_id, "BRC rides the reopened train").toBe( scheduleId, ); }); });