mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 20:05:41 +00:00
145 lines
5.4 KiB
TypeScript
145 lines
5.4 KiB
TypeScript
import { DataSource, Repository } from 'typeorm';
|
|
|
|
import { Booking } from './entities/booking.entity';
|
|
import { BookingsRepository } from './bookings.repository';
|
|
|
|
function mockQueryBuilder() {
|
|
const qb = {
|
|
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
leftJoin: jest.fn().mockReturnThis(),
|
|
addSelect: jest.fn().mockReturnThis(),
|
|
where: jest.fn().mockReturnThis(),
|
|
andWhere: jest.fn().mockReturnThis(),
|
|
orderBy: jest.fn().mockReturnThis(),
|
|
addOrderBy: jest.fn().mockReturnThis(),
|
|
skip: jest.fn().mockReturnThis(),
|
|
take: jest.fn().mockReturnThis(),
|
|
getMany: jest.fn(),
|
|
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
|
|
getCount: jest.fn().mockResolvedValue(0),
|
|
getRawAndEntities: jest.fn().mockResolvedValue({ entities: [], raw: [] }),
|
|
};
|
|
return qb;
|
|
}
|
|
|
|
describe('BookingsRepository', () => {
|
|
let repository: jest.Mocked<Repository<Booking>>;
|
|
let dataSource: { getRepository: jest.Mock };
|
|
let bookingsRepository: BookingsRepository;
|
|
|
|
beforeEach(() => {
|
|
repository = {
|
|
createQueryBuilder: jest.fn(),
|
|
} as unknown as jest.Mocked<Repository<Booking>>;
|
|
dataSource = { getRepository: jest.fn() };
|
|
bookingsRepository = new BookingsRepository(repository, dataSource as unknown as DataSource);
|
|
});
|
|
|
|
it('findEligibleForScheduling does not filter by schedule date', async () => {
|
|
const qb = mockQueryBuilder();
|
|
const bookings = [
|
|
{ id: 'b1', scheduledDate: new Date('2026-06-20T08:00:00.000Z') },
|
|
{ id: 'b2', scheduledDate: new Date('2026-06-21T14:00:00.000Z') },
|
|
];
|
|
qb.getMany.mockResolvedValue(bookings);
|
|
repository.createQueryBuilder.mockReturnValue(qb as never);
|
|
|
|
const result = await bookingsRepository.findEligibleForScheduling({
|
|
originStationId: 'yard-origin',
|
|
destinationStationId: 'yard-destination',
|
|
freightType: 'CONTAINER',
|
|
});
|
|
|
|
expect(result).toHaveLength(2);
|
|
const dateFilters = qb.andWhere.mock.calls.filter(([clause]) =>
|
|
String(clause).includes('scheduled_date'),
|
|
);
|
|
expect(dateFilters).toHaveLength(0);
|
|
});
|
|
|
|
it('applyListFilters excludes assigned bookings when assignedToSchedule is false', async () => {
|
|
const qb = mockQueryBuilder();
|
|
repository.createQueryBuilder.mockReturnValue(qb as never);
|
|
dataSource.getRepository.mockReturnValue({ find: jest.fn().mockResolvedValue([]) });
|
|
|
|
await bookingsRepository.findAllPaginated({
|
|
page: 1,
|
|
pageSize: 10,
|
|
assignedToSchedule: 'false',
|
|
});
|
|
|
|
expect(qb.andWhere).toHaveBeenCalledWith(expect.stringContaining('NOT EXISTS'));
|
|
});
|
|
|
|
it('findManualConsolidationCandidates offers an odd-20ft partner booked on another day', async () => {
|
|
const qb = mockQueryBuilder();
|
|
// Same route/direction, odd 20ft count, but sitting on a different
|
|
// scheduled_date than the booking being completed. GL completes both halves
|
|
// onto the date chosen on the form, so this is still a legal partner.
|
|
qb.getMany.mockResolvedValue([
|
|
{
|
|
id: 'partner',
|
|
reference: 'BK-2026-000303',
|
|
scheduledDate: new Date('2026-09-02T08:00:00.000Z'),
|
|
bookingContainers: [{ quantity: 1, containerType: { sizeFt: 20 } }],
|
|
},
|
|
]);
|
|
repository.createQueryBuilder.mockReturnValue(qb as never);
|
|
|
|
const result = await bookingsRepository.findManualConsolidationCandidates({
|
|
id: 'own',
|
|
originYardId: 'yard-a',
|
|
destinationYardId: 'yard-b',
|
|
tradeDirection: 'IMPORT',
|
|
scheduledDate: new Date('2026-09-01T08:00:00.000Z'),
|
|
} as Booking);
|
|
|
|
expect(result.map((r) => r.booking.reference)).toEqual(['BK-2026-000303']);
|
|
expect(result[0].ft20Quantity).toBe(1);
|
|
// The booking day must not narrow this list at all.
|
|
const dateFilters = qb.andWhere.mock.calls.filter(([clause]) =>
|
|
String(clause).includes('scheduled_date'),
|
|
);
|
|
expect(dateFilters).toHaveLength(0);
|
|
});
|
|
|
|
it('findManualConsolidationCandidates falls back to the requested lines before cargo is persisted', async () => {
|
|
const qb = mockQueryBuilder();
|
|
// The ordinary state of a CLEARANCE_READY customs booking: container lines
|
|
// are written by completion, so there are none yet and the accepted booking
|
|
// request is the only statement of what it will carry.
|
|
qb.getMany.mockResolvedValue([
|
|
{ id: 'b-odd', reference: 'BK-2026-001116', bookingContainers: [] },
|
|
{ id: 'b-even', reference: 'BK-EVEN', bookingContainers: [] },
|
|
// No request at all — count unknown, so not offerable.
|
|
{ id: 'b-unknown', reference: 'BK-UNKNOWN', bookingContainers: [] },
|
|
]);
|
|
repository.createQueryBuilder.mockReturnValue(qb as never);
|
|
dataSource.getRepository.mockReturnValue({
|
|
createQueryBuilder: () => ({
|
|
where: jest.fn().mockReturnThis(),
|
|
getMany: jest.fn().mockResolvedValue([
|
|
{
|
|
createdBookingId: 'b-odd',
|
|
requestedLines: { containers: [{ containerSize: '20ft', quantity: 1 }] },
|
|
},
|
|
{
|
|
createdBookingId: 'b-even',
|
|
requestedLines: { containers: [{ containerSize: '20ft', quantity: 2 }] },
|
|
},
|
|
]),
|
|
}),
|
|
});
|
|
|
|
const result = await bookingsRepository.findManualConsolidationCandidates({
|
|
id: 'own',
|
|
originYardId: 'yard-a',
|
|
destinationYardId: 'yard-b',
|
|
tradeDirection: 'EXPORT',
|
|
} as Booking);
|
|
|
|
expect(result.map((r) => r.booking.reference)).toEqual(['BK-2026-001116']);
|
|
expect(result[0].ft20Quantity).toBe(1);
|
|
});
|
|
});
|