mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 23:35:42 +00:00
feat: integration tests
This commit is contained in:
127
integration/src/bulk-import-window-reopen.it.ts
Normal file
127
integration/src/bulk-import-window-reopen.it.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user