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

128 lines
4.1 KiB
TypeScript

/**
* 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<string, string>();
let contracts: Map<string, string>;
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,
);
});
});