mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 09:42:53 +00:00
128 lines
4.1 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|