mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +00:00
Add a minimal payment mock server for end-to-end testing
This commit is contained in:
@@ -53,7 +53,7 @@ import {
|
||||
seedImportContract,
|
||||
withBooking,
|
||||
} from "./import-utils";
|
||||
import { bookAndClear } from "./g1-utils";
|
||||
import { bookAndClear, clearAndAccept } from "./g1-utils";
|
||||
|
||||
const stamp = String(Date.now());
|
||||
const stampedRef = (suffix: string) => `CTR-IMP-${stamp}-${suffix}`;
|
||||
@@ -173,15 +173,16 @@ describe("G3·S13: unpaid export holds occupy the train", { retries: 0 }, () =>
|
||||
});
|
||||
|
||||
it("EA holds 30 wagons; EB then sees only 24 free and holds 20", () => {
|
||||
bookContainers({
|
||||
// Export: ACCEPT is the reservation (FCFS), not a batch entry — but the
|
||||
// booking still clears its per-booking document gate first, same as import.
|
||||
bookAndClear({
|
||||
suffix: "EA",
|
||||
runStamp: stamp,
|
||||
isoSeed: 14_100,
|
||||
forty: SHAPES.EA.forty,
|
||||
scheduledDate: BOOKING_DAY,
|
||||
mode: "export",
|
||||
});
|
||||
// Export: ACCEPT is the reservation (FCFS), not a batch entry.
|
||||
acceptExport("EA");
|
||||
|
||||
bookAndClear({
|
||||
suffix: "EB",
|
||||
@@ -403,7 +404,7 @@ describe("G3·S15: two trains on one day, picked per booking", { retries: 0 }, (
|
||||
});
|
||||
|
||||
it("A fills most of T1; B and C then choose by what is left", () => {
|
||||
acceptExport("PA");
|
||||
clearAndAccept({ suffix: "PA", scheduledDate: BOOKING_DAY, mode: "export" });
|
||||
markPaid("PA");
|
||||
pollAllocations("PA", SHAPES.PA.wagons);
|
||||
|
||||
@@ -473,15 +474,15 @@ describe("G3·S17: a clearance hold gates one booking only", { retries: 0 }, ()
|
||||
|
||||
let isoSeed = 16_600;
|
||||
ORDER.forEach((suffix) => {
|
||||
bookContainers({
|
||||
bookAndClear({
|
||||
suffix,
|
||||
runStamp: stamp,
|
||||
isoSeed,
|
||||
forty: SHAPES[suffix].forty,
|
||||
scheduledDate: BOOKING_DAY,
|
||||
mode: "export",
|
||||
});
|
||||
isoSeed += SHAPES[suffix].forty;
|
||||
acceptExport(suffix);
|
||||
markPaid(suffix);
|
||||
pollAllocations(suffix, SHAPES[suffix].wagons);
|
||||
});
|
||||
@@ -605,7 +606,7 @@ describe("G3·S16: an expired hold frees space and fits flips back", { retries:
|
||||
}),
|
||||
);
|
||||
|
||||
acceptExport("HC");
|
||||
clearAndAccept({ suffix: "HC", scheduledDate: BOOKING_DAY, mode: "export" });
|
||||
markPaid("HC");
|
||||
pollAllocations("HC", SHAPES.HC.wagons);
|
||||
});
|
||||
|
||||
@@ -122,15 +122,13 @@ describe("G5·S22: an expired booking moves to the next day intact", { retries:
|
||||
|
||||
// The same customer, the same contract, a new day — straight back into
|
||||
// OPERATION_REQUEST_PENDING as if nothing had gone wrong.
|
||||
bookContainers({
|
||||
bookAndClear({
|
||||
suffix: "RC",
|
||||
runStamp: stamp,
|
||||
isoSeed: 18_400,
|
||||
forty: SHAPES.RC.forty,
|
||||
scheduledDate: BOOKING_DAY_2,
|
||||
});
|
||||
pollBookingStatus("RC", "OPERATION_REQUEST_PENDING", 10);
|
||||
acceptOperation("RC");
|
||||
});
|
||||
|
||||
it("day 2 carries RC plus a new 40-wagon customer — FULL at 53/53", () => {
|
||||
@@ -231,15 +229,13 @@ describe("G5·S23: a split's paid part survives the remainder expiring", { retri
|
||||
configureAndOpenSchedule({ departure: DAY_2, trainCode: G1_TRAIN_2 });
|
||||
|
||||
// A split customer must rebook EXACTLY the outstanding remainder.
|
||||
bookContainers({
|
||||
bookAndClear({
|
||||
suffix: "SC",
|
||||
runStamp: stamp,
|
||||
isoSeed: 19_200,
|
||||
twenty: REMAINDER_CONTAINERS,
|
||||
scheduledDate: BOOKING_DAY_2,
|
||||
});
|
||||
pollBookingStatus("SC", "OPERATION_REQUEST_PENDING", 10);
|
||||
acceptOperation("SC");
|
||||
closeWindowAndRunBatch(DAY_2);
|
||||
pollBookingStatus("SC", ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]);
|
||||
|
||||
|
||||
@@ -539,7 +539,9 @@ export function clearToOperationRequestPending(suffix: string, scheduledDate: st
|
||||
expect(b.status, `${suffix} starts in the clearance gate`).to.eq("AWAITING_DOCUMENTS");
|
||||
clearBookingClearance(b.id, scheduledDate);
|
||||
});
|
||||
pollBookingStatus(suffix, "OPERATION_REQUEST_PENDING", 5);
|
||||
// 20 attempts ≈ 60s: the gate runs four sequential API calls (upload →
|
||||
// review → finalize → proceed) and the status only settles after the last.
|
||||
pollBookingStatus(suffix, "OPERATION_REQUEST_PENDING", 20);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -725,20 +727,23 @@ export function settleViaGateway(suffix: string) {
|
||||
export function forceReservationExpiry(suffix: string) {
|
||||
withBooking(suffix, (b) =>
|
||||
db(
|
||||
// A full HOUR into the past, not one second.
|
||||
//
|
||||
// The sweep races the top-up it triggers: promoting a waiting booking
|
||||
// calls extendPaymentPhaseForTopUp (booking-batch.service.ts:2795),
|
||||
// which pushes paymentPhaseEndsAt out — and a deadline only just behind
|
||||
// `now()` can end up on the wrong side of the moved boundary, leaving
|
||||
// the reservation un-flipped while its wagons have already been handed
|
||||
// to the waiting list. An hour is unambiguously overdue under any
|
||||
// extension the top-up applies.
|
||||
// An hour into the past, not one second: the settle races the top-up it
|
||||
// triggers, and promoting a waiting booking calls
|
||||
// extendPaymentPhaseForTopUp (booking-batch.service.ts:2795) which
|
||||
// pushes the phase boundary out. A deadline only just behind `now()` can
|
||||
// land on the wrong side of that move.
|
||||
`UPDATE freight.bookings SET payment_deadline = now() - interval '1 hour'
|
||||
WHERE id = $1`,
|
||||
[b.id],
|
||||
),
|
||||
);
|
||||
// NOTE: this only settles because the e2e stack runs a payment-service mock
|
||||
// (docker-compose.e2e.yaml → payment-mock-e2e). Before expiring an unpaid
|
||||
// hold the engine asks the gateway whether a late payment landed, and treats
|
||||
// an unreachable gateway as "unverifiable" — deferring the expiry forever
|
||||
// rather than risking expiring someone who paid. Against a stack without
|
||||
// PAYMENT_API_URL pointed at a reachable service, this poll times out and
|
||||
// the API logs "expire deferred … settlement unverifiable at the gateway".
|
||||
pollBookingStatus(suffix, "EXPIRED");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user