mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-02 08:03:42 +00:00
changes
This commit is contained in:
@@ -22,6 +22,7 @@ describe('BookingBatchService — PAID reconcile', () => {
|
||||
findBatchPool: jest.Mock;
|
||||
findBatchPoolByRouteDay: jest.Mock;
|
||||
findBatchPoolByCorridorDay: jest.Mock;
|
||||
findUnacceptedForRouteDay: jest.Mock;
|
||||
findReservedForSchedule: jest.Mock;
|
||||
update: jest.Mock;
|
||||
};
|
||||
@@ -55,6 +56,7 @@ describe('BookingBatchService — PAID reconcile', () => {
|
||||
findBatchPool: jest.fn().mockResolvedValue([]),
|
||||
findBatchPoolByRouteDay: jest.fn().mockResolvedValue([]),
|
||||
findBatchPoolByCorridorDay: jest.fn().mockResolvedValue([]),
|
||||
findUnacceptedForRouteDay: jest.fn().mockResolvedValue([]),
|
||||
findReservedForSchedule: jest.fn().mockResolvedValue([]),
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
@@ -332,4 +334,177 @@ describe('BookingBatchService — PAID reconcile', () => {
|
||||
expect(notifier.payNow).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('expireUnacceptedForRouteDay — doc-review sweep', () => {
|
||||
const originYardId = 'yard-origin';
|
||||
const destinationYardId = 'yard-dest';
|
||||
const day = '2026-06-20';
|
||||
|
||||
const pendingBooking = {
|
||||
id: 'pending-1',
|
||||
reference: 'BK-PENDING-1',
|
||||
status: 'OPERATION_REQUEST_PENDING',
|
||||
isGovernment: false,
|
||||
originYardId,
|
||||
destinationYardId,
|
||||
} as unknown as Booking;
|
||||
|
||||
beforeEach(() => {
|
||||
// One fillable schedule on this corridor/day so corridorYardsForRouteDay
|
||||
// resolves a non-empty yard set (legacy two-stop route → [origin, dest]).
|
||||
trainSchedulesRepository.findAll.mockResolvedValue([
|
||||
{
|
||||
id: 'sched-1',
|
||||
originStationId: originYardId,
|
||||
destinationStationId: destinationYardId,
|
||||
scheduledDepartureDate: new Date('2026-06-20T06:00:00.000Z'),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('expires each un-accepted booking and clears its scheduled day', async () => {
|
||||
bookingsRepository.findUnacceptedForRouteDay.mockResolvedValue([pendingBooking]);
|
||||
|
||||
await service.expireUnacceptedForRouteDay({
|
||||
originYardId,
|
||||
destinationYardId,
|
||||
day,
|
||||
});
|
||||
|
||||
expect(bookingsRepository.findUnacceptedForRouteDay).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([originYardId, destinationYardId]),
|
||||
day,
|
||||
);
|
||||
expect(bookingsRepository.update).toHaveBeenCalledWith(
|
||||
'pending-1',
|
||||
expect.objectContaining({
|
||||
status: 'EXPIRED',
|
||||
schedulingStatus: 'ELIGIBLE',
|
||||
scheduledDate: null,
|
||||
}),
|
||||
);
|
||||
expect(notifier.expired).toHaveBeenCalledWith(pendingBooking);
|
||||
});
|
||||
|
||||
it('is a no-op when nothing is un-accepted', async () => {
|
||||
bookingsRepository.findUnacceptedForRouteDay.mockResolvedValue([]);
|
||||
|
||||
await service.expireUnacceptedForRouteDay({
|
||||
originYardId,
|
||||
destinationYardId,
|
||||
day,
|
||||
});
|
||||
|
||||
expect(bookingsRepository.update).not.toHaveBeenCalled();
|
||||
expect(notifier.expired).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does nothing when the route-day has no fillable schedule', async () => {
|
||||
trainSchedulesRepository.findAll.mockResolvedValue([]);
|
||||
|
||||
await service.expireUnacceptedForRouteDay({
|
||||
originYardId,
|
||||
destinationYardId,
|
||||
day,
|
||||
});
|
||||
|
||||
expect(bookingsRepository.findUnacceptedForRouteDay).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('maybeOfferPartial — split-eligibility gate', () => {
|
||||
const importGeneral = {
|
||||
id: 'b1',
|
||||
reference: 'b1',
|
||||
isGovernment: false,
|
||||
tradeDirection: 'IMPORT',
|
||||
contractKind: 'GENERAL',
|
||||
consolidationPartnerId: null,
|
||||
} as unknown as Booking;
|
||||
|
||||
const call = (booking: Booking, isPair: boolean): boolean =>
|
||||
(
|
||||
service as unknown as {
|
||||
isSplitEligible: (b: Booking, p: boolean) => boolean;
|
||||
}
|
||||
).isSplitEligible(booking, isPair);
|
||||
|
||||
it('allows IMPORT + GENERAL when splitService is present', () => {
|
||||
const withSplit = new BookingBatchService(
|
||||
dataSource as never,
|
||||
bookingsRepository as never,
|
||||
trainSchedulesRepository as never,
|
||||
trainScheduleBookingsRepository as never,
|
||||
notifier as never,
|
||||
{ addTimeout: jest.fn(), deleteTimeout: jest.fn(), doesExist: jest.fn() } as never,
|
||||
trainSchedulingService as never,
|
||||
{ syncPayableDueDate: jest.fn(), expirePayable: jest.fn() } as never,
|
||||
{ emitPhase: jest.fn() } as never,
|
||||
undefined,
|
||||
{ findOpenOffer: jest.fn() } as never,
|
||||
);
|
||||
const eligible = (
|
||||
withSplit as unknown as {
|
||||
isSplitEligible: (b: Booking, p: boolean) => boolean;
|
||||
}
|
||||
).isSplitEligible(importGeneral, false);
|
||||
expect(eligible).toBe(true);
|
||||
});
|
||||
|
||||
it('allows IMPORT + ONE_TIME (promoted to GENERAL on split)', () => {
|
||||
const withSplit = new BookingBatchService(
|
||||
dataSource as never,
|
||||
bookingsRepository as never,
|
||||
trainSchedulesRepository as never,
|
||||
trainScheduleBookingsRepository as never,
|
||||
notifier as never,
|
||||
{ addTimeout: jest.fn(), deleteTimeout: jest.fn(), doesExist: jest.fn() } as never,
|
||||
trainSchedulingService as never,
|
||||
{ syncPayableDueDate: jest.fn(), expirePayable: jest.fn() } as never,
|
||||
{ emitPhase: jest.fn() } as never,
|
||||
undefined,
|
||||
{ findOpenOffer: jest.fn() } as never,
|
||||
);
|
||||
const eligible = (
|
||||
withSplit as unknown as {
|
||||
isSplitEligible: (b: Booking, p: boolean) => boolean;
|
||||
}
|
||||
).isSplitEligible(
|
||||
{ ...importGeneral, contractKind: 'ONE_TIME' } as Booking,
|
||||
false,
|
||||
);
|
||||
expect(eligible).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects when splitService is absent (default test service)', () => {
|
||||
// `service` from the outer beforeEach was built without a splitService.
|
||||
expect(call(importGeneral, false)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects EXPORT, government, consolidated pairs, and other directions', () => {
|
||||
const withSplit = new BookingBatchService(
|
||||
dataSource as never,
|
||||
bookingsRepository as never,
|
||||
trainSchedulesRepository as never,
|
||||
trainScheduleBookingsRepository as never,
|
||||
notifier as never,
|
||||
{ addTimeout: jest.fn(), deleteTimeout: jest.fn(), doesExist: jest.fn() } as never,
|
||||
trainSchedulingService as never,
|
||||
{ syncPayableDueDate: jest.fn(), expirePayable: jest.fn() } as never,
|
||||
{ emitPhase: jest.fn() } as never,
|
||||
undefined,
|
||||
{ findOpenOffer: jest.fn() } as never,
|
||||
);
|
||||
const check = (
|
||||
withSplit as unknown as {
|
||||
isSplitEligible: (b: Booking, p: boolean) => boolean;
|
||||
}
|
||||
).isSplitEligible.bind(withSplit);
|
||||
|
||||
expect(check({ ...importGeneral, tradeDirection: 'EXPORT' } as Booking, false)).toBe(false);
|
||||
expect(check({ ...importGeneral, isGovernment: true } as Booking, false)).toBe(false);
|
||||
expect(check(importGeneral, true)).toBe(false); // consolidated pair
|
||||
expect(check({ ...importGeneral, contractKind: null } as Booking, false)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user