mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 00:50:56 +00:00
141 lines
5.7 KiB
TypeScript
141 lines
5.7 KiB
TypeScript
import {
|
||
getBatchWindowForTimestamp,
|
||
listBatchWindowsForDate,
|
||
listBatchWindowsForBookings,
|
||
BATCH_WINDOW_START_HOURS,
|
||
listConfigBookingWindows,
|
||
groupBookingsIntoBoardWindows,
|
||
type BoardWindowConfig,
|
||
} from './batch-window.util';
|
||
|
||
describe('batch-window.util', () => {
|
||
it('maps 20:15 EAT to the 19:00–22:00 window', () => {
|
||
// 20:15 EAT = 17:15 UTC on 11 Jun 2026
|
||
const ts = new Date('2026-06-11T17:15:00.000Z');
|
||
const window = getBatchWindowForTimestamp(ts);
|
||
|
||
expect(window.label).toContain('19:00');
|
||
expect(window.label).toContain('22:00');
|
||
expect(window.label).toContain('11 Jun 2026');
|
||
});
|
||
|
||
it('maps 08:30 EAT to the 07:00–10:00 window', () => {
|
||
const ts = new Date('2026-06-11T05:30:00.000Z'); // 08:30 EAT
|
||
const window = getBatchWindowForTimestamp(ts);
|
||
expect(window.label).toContain('07:00');
|
||
expect(window.label).toContain('10:00');
|
||
});
|
||
|
||
it('maps 02:00 EAT to the previous day 22:00–07:00 window', () => {
|
||
const ts = new Date('2026-06-11T23:00:00.000Z'); // 02:00 EAT on 12 Jun
|
||
const window = getBatchWindowForTimestamp(ts);
|
||
expect(window.label).toContain('22:00');
|
||
expect(window.label).toContain('07:00');
|
||
expect(window.label).toContain('11 Jun 2026');
|
||
});
|
||
|
||
it('lists six windows for a calendar day', () => {
|
||
const ref = new Date('2026-06-11T12:00:00.000Z');
|
||
const windows = listBatchWindowsForDate(ref);
|
||
expect(windows).toHaveLength(BATCH_WINDOW_START_HOURS.length);
|
||
expect(windows[0].label).toContain('07:00');
|
||
expect(windows[windows.length - 1].label).toContain('22:00');
|
||
});
|
||
|
||
it('includes cross-day overnight window when booking signed at 00:02 EAT', () => {
|
||
// 21:02 UTC = 00:02 EAT on 12 Jun → belongs to 11 Jun 22:00–07:00 window
|
||
const fullyExecutedAt = new Date('2026-06-11T21:02:05.153Z');
|
||
const scheduleDate = new Date('2026-06-12T06:00:00.000Z');
|
||
const windows = listBatchWindowsForBookings([fullyExecutedAt], scheduleDate);
|
||
const overnight = windows.find((w) => w.label.includes('22:00') && w.label.includes('07:00'));
|
||
expect(overnight).toBeDefined();
|
||
expect(overnight!.label).toContain('11 Jun 2026');
|
||
expect(getBatchWindowForTimestamp(fullyExecutedAt).key).toBe(overnight!.key);
|
||
});
|
||
});
|
||
|
||
describe('batch-window board windows (config-driven booking cycles)', () => {
|
||
// Default rules: open 08:00 EAT, 3 days before departure, 3h long, reopen 90m later.
|
||
const cfg: BoardWindowConfig = {
|
||
importWindowLeadDays: 3,
|
||
windowOpenHour: 8,
|
||
windowDurationHours: 3,
|
||
reopenDelayMinutes: 90,
|
||
exportBookingLeadHours: 24,
|
||
};
|
||
|
||
it('import: first window opens at windowOpenHour EAT, importWindowLeadDays before departure', () => {
|
||
// departs 08 Jun 14:00 EAT (11:00 UTC) → window day = 05 Jun, opens 08:00 EAT (05:00 UTC)
|
||
const departure = new Date('2026-06-08T11:00:00.000Z');
|
||
const windows = listConfigBookingWindows('IMPORT', departure, cfg);
|
||
|
||
expect(windows[0].date).toBe('2026-06-05');
|
||
expect(windows[0].label).toContain('08:00');
|
||
expect(windows[0].start.toISOString()).toBe('2026-06-05T05:00:00.000Z');
|
||
// end = open + windowDurationHours (3h) = 08:00 → 11:00 EAT (08:00 UTC)
|
||
expect(windows[0].end.toISOString()).toBe('2026-06-05T08:00:00.000Z');
|
||
});
|
||
|
||
it('import: reopens reopenDelayMinutes after close, same booking day', () => {
|
||
const departure = new Date('2026-06-08T11:00:00.000Z');
|
||
const windows = listConfigBookingWindows('IMPORT', departure, cfg);
|
||
// cycle 1: 08:00–11:00; reopen +90m → cycle 2 opens 12:30 EAT
|
||
expect(windows.length).toBeGreaterThanOrEqual(2);
|
||
expect(windows[1].start.toISOString()).toBe('2026-06-05T09:30:00.000Z'); // 12:30 EAT
|
||
// all cycles stay on the same EAT booking day
|
||
expect(windows.every((w) => w.date === '2026-06-05')).toBe(true);
|
||
});
|
||
|
||
it('export: single FCFS window exportBookingLeadHours before departure', () => {
|
||
const departure = new Date('2026-06-08T11:00:00.000Z');
|
||
const windows = listConfigBookingWindows('EXPORT', departure, cfg);
|
||
expect(windows).toHaveLength(1);
|
||
// 24h before 11:00 UTC on 08 Jun = 11:00 UTC on 07 Jun
|
||
expect(windows[0].start.toISOString()).toBe('2026-06-07T11:00:00.000Z');
|
||
expect(windows[0].end.toISOString()).toBe(departure.toISOString());
|
||
});
|
||
|
||
it('buckets bookings into config cycles and keeps empty + pending windows', () => {
|
||
const departure = new Date('2026-06-08T11:00:00.000Z');
|
||
const items = [
|
||
{ id: 'a', ts: new Date('2026-06-05T05:30:00.000Z') }, // 08:30 EAT → inside cycle 1
|
||
{ id: 'b', ts: null }, // pending
|
||
];
|
||
const map = groupBookingsIntoBoardWindows(
|
||
items,
|
||
(i) => i.ts,
|
||
'IMPORT',
|
||
departure,
|
||
cfg,
|
||
'pending-contract',
|
||
);
|
||
const pending = map.get('pending-contract');
|
||
expect(pending?.items.map((i) => i.id)).toEqual(['b']);
|
||
const withA = [...map.values()].find((b) => b.items.some((i) => i.id === 'a'));
|
||
expect(withA?.window?.date).toBe('2026-06-05');
|
||
// empty cycles are retained for the UI
|
||
const emptyCount = [...map.values()].filter(
|
||
(b) => b.window && b.items.length === 0,
|
||
).length;
|
||
expect(emptyCount).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('attaches a booking made before the window opened to the first cycle', () => {
|
||
const departure = new Date('2026-06-08T11:00:00.000Z');
|
||
const items = [{ id: 'early', ts: new Date('2026-06-01T00:00:00.000Z') }];
|
||
const map = groupBookingsIntoBoardWindows(
|
||
items,
|
||
(i) => i.ts,
|
||
'IMPORT',
|
||
departure,
|
||
cfg,
|
||
'pending-contract',
|
||
);
|
||
const withEarly = [...map.values()].find((b) =>
|
||
b.items.some((i) => i.id === 'early'),
|
||
);
|
||
expect(withEarly?.window?.date).toBe('2026-06-05');
|
||
expect(withEarly?.window?.label).toContain('08:00');
|
||
});
|
||
});
|