mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 11:21:18 +00:00
145 lines
5.1 KiB
TypeScript
145 lines
5.1 KiB
TypeScript
import { BookingBatchService } from './booking-batch.service';
|
|
import { Booking } from '../bookings/entities/booking.entity';
|
|
|
|
describe('BookingBatchService — PAID reconcile', () => {
|
|
const scheduleId = 'schedule-1';
|
|
const bookingId = 'booking-1';
|
|
|
|
const paidBooking = {
|
|
id: bookingId,
|
|
reference: 'BK-2026-000034',
|
|
trainScheduleId: scheduleId,
|
|
status: 'PAID',
|
|
paymentStatus: 'PAID',
|
|
isGovernment: false,
|
|
cargoTotalWeightVgm: 20,
|
|
bookingContainers: [],
|
|
} as unknown as Booking;
|
|
|
|
let service: BookingBatchService;
|
|
let bookingsRepository: {
|
|
findPaidUnlinkedForSchedule: jest.Mock;
|
|
findBatchPool: jest.Mock;
|
|
findReservedForSchedule: jest.Mock;
|
|
update: jest.Mock;
|
|
};
|
|
let trainScheduleBookingsRepository: {
|
|
existsForBooking: jest.Mock;
|
|
createMany: jest.Mock;
|
|
};
|
|
let trainSchedulesRepository: {
|
|
findByIdWithFullGraph: jest.Mock;
|
|
findAll: jest.Mock;
|
|
};
|
|
let trainSchedulingService: {
|
|
tryAutoWagonAllocation: jest.Mock;
|
|
};
|
|
let dataSource: {
|
|
getRepository: jest.Mock;
|
|
transaction: jest.Mock;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
bookingsRepository = {
|
|
findPaidUnlinkedForSchedule: jest.fn().mockResolvedValue([]),
|
|
findBatchPool: jest.fn().mockResolvedValue([]),
|
|
findReservedForSchedule: jest.fn().mockResolvedValue([]),
|
|
update: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
trainScheduleBookingsRepository = {
|
|
existsForBooking: jest.fn().mockResolvedValue(false),
|
|
createMany: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
trainSchedulesRepository = {
|
|
findByIdWithFullGraph: jest.fn().mockResolvedValue({
|
|
id: scheduleId,
|
|
maxWagons: 10,
|
|
bookingWindowStatus: 'OPEN',
|
|
trainSet: { locomotive: { maxPullWeightTons: 3500, maxTrainLengthMeters: 760 } },
|
|
scheduleBookings: [],
|
|
}),
|
|
findAll: jest.fn().mockResolvedValue([]),
|
|
};
|
|
trainSchedulingService = {
|
|
tryAutoWagonAllocation: jest.fn().mockResolvedValue({
|
|
assignedBookingIds: [],
|
|
deferred: [],
|
|
issues: [],
|
|
violations: [],
|
|
}),
|
|
};
|
|
|
|
const bookingRepo = {
|
|
findOne: jest.fn().mockResolvedValue(paidBooking),
|
|
update: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
dataSource = {
|
|
getRepository: jest.fn().mockReturnValue(bookingRepo),
|
|
transaction: jest.fn(async (fn: (m: unknown) => Promise<void>) => {
|
|
const manager = {
|
|
getRepository: () => bookingRepo,
|
|
};
|
|
await fn(manager);
|
|
}),
|
|
};
|
|
|
|
service = new BookingBatchService(
|
|
dataSource as never,
|
|
bookingsRepository as never,
|
|
trainSchedulesRepository as never,
|
|
trainScheduleBookingsRepository as never,
|
|
{ payNow: jest.fn(), secured: jest.fn(), expired: jest.fn() } as never,
|
|
{ addTimeout: jest.fn(), deleteTimeout: jest.fn(), doesExist: jest.fn() } as never,
|
|
trainSchedulingService as never,
|
|
);
|
|
});
|
|
|
|
it('reconcilePaidUnlinked links PAID bookings without a schedule row', async () => {
|
|
bookingsRepository.findPaidUnlinkedForSchedule.mockResolvedValue([paidBooking]);
|
|
|
|
await service.reconcilePaidUnlinked(scheduleId);
|
|
|
|
expect(bookingsRepository.findPaidUnlinkedForSchedule).toHaveBeenCalledWith(scheduleId);
|
|
expect(trainScheduleBookingsRepository.createMany).toHaveBeenCalledWith(
|
|
[{ trainScheduleId: scheduleId, bookingId }],
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('ensurePaidBookingAllocated links PAID booking when not yet linked', async () => {
|
|
await service.ensurePaidBookingAllocated(bookingId);
|
|
|
|
expect(trainScheduleBookingsRepository.createMany).toHaveBeenCalledTimes(1);
|
|
expect(trainSchedulingService.tryAutoWagonAllocation).toHaveBeenCalledWith(scheduleId);
|
|
});
|
|
|
|
it('ensurePaidBookingAllocated is idempotent when already linked', async () => {
|
|
trainScheduleBookingsRepository.existsForBooking.mockResolvedValue(true);
|
|
|
|
await service.ensurePaidBookingAllocated(bookingId);
|
|
await service.ensurePaidBookingAllocated(bookingId);
|
|
|
|
expect(trainScheduleBookingsRepository.createMany).not.toHaveBeenCalled();
|
|
expect(trainSchedulingService.tryAutoWagonAllocation).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('processSchedule reconciles PAID-unlinked before wagon allocation', async () => {
|
|
const fillSpy = jest.spyOn(service, 'fillSchedule').mockResolvedValue(undefined);
|
|
const settleSpy = jest.spyOn(service, 'settleDueReservations').mockResolvedValue(undefined);
|
|
const reconcileSpy = jest.spyOn(service, 'reconcilePaidUnlinked').mockResolvedValue(undefined);
|
|
|
|
await service.processSchedule(scheduleId);
|
|
|
|
expect(fillSpy).toHaveBeenCalledWith(scheduleId);
|
|
expect(settleSpy).toHaveBeenCalledWith(scheduleId);
|
|
expect(reconcileSpy).toHaveBeenCalledWith(scheduleId);
|
|
expect(trainSchedulingService.tryAutoWagonAllocation).toHaveBeenCalledWith(scheduleId);
|
|
|
|
const fillOrder = fillSpy.mock.invocationCallOrder[0];
|
|
const reconcileOrder = reconcileSpy.mock.invocationCallOrder[0];
|
|
const wagonOrder = trainSchedulingService.tryAutoWagonAllocation.mock.invocationCallOrder[0];
|
|
expect(fillOrder).toBeLessThan(reconcileOrder);
|
|
expect(reconcileOrder).toBeLessThan(wagonOrder);
|
|
});
|
|
});
|