updated the test
This commit is contained in:
138
tests/lib/batchBucket.test.js
Normal file
138
tests/lib/batchBucket.test.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import { BATCHES, getBatchForHour, batchRangeLabel, getRowBatchId, getBatchLabel } from '@/lib/batchBucket';
|
||||
|
||||
/**
|
||||
* Morning/Afternoon/Evening wave bucketing.
|
||||
*
|
||||
* The bug these tests exist to prevent: the windows used to cover only 14.5 of
|
||||
* 24 hours, so an order placed at 2:43pm belonged to no batch and vanished from
|
||||
* every batch filter. The invariant is TOTAL COVERAGE — every hour of the day
|
||||
* lands in exactly one batch — plus the field the bucketing reads (`orderdate`,
|
||||
* never `assigntime` or `expecteddeliverytime`).
|
||||
*/
|
||||
describe('batchBucket', () => {
|
||||
describe('BATCHES definition', () => {
|
||||
it('should define exactly the three operator-facing waves', () => {
|
||||
expect(BATCHES.map((b) => b.id)).toEqual(['morning', 'afternoon', 'evening']);
|
||||
});
|
||||
|
||||
it('should cover the whole day with no gap and no overlap', () => {
|
||||
const sorted = [...BATCHES].sort((a, b) => a.startHour - b.startHour);
|
||||
expect(sorted[0].startHour).toBe(0);
|
||||
expect(sorted[sorted.length - 1].endHour).toBe(24);
|
||||
sorted.slice(1).forEach((batch, index) => {
|
||||
expect(batch.startHour).toBe(sorted[index].endHour);
|
||||
});
|
||||
});
|
||||
|
||||
it('should assign every quarter-hour of the day to exactly one batch', () => {
|
||||
for (let hour = 0; hour < 24; hour += 0.25) {
|
||||
const matches = BATCHES.filter((b) => hour >= b.startHour && hour < b.endHour);
|
||||
expect(matches).toHaveLength(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBatchForHour', () => {
|
||||
it.each([
|
||||
[0, 'morning'],
|
||||
[8.99, 'morning'],
|
||||
[9, 'afternoon'],
|
||||
[14.716, 'afternoon'],
|
||||
[15.99, 'afternoon'],
|
||||
[16, 'evening'],
|
||||
[23.99, 'evening']
|
||||
])('should bucket hour %p into %s', (hour, expected) => {
|
||||
expect(getBatchForHour(hour)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should treat every window as half-open [start, end)', () => {
|
||||
expect(getBatchForHour(9)).toBe('afternoon');
|
||||
expect(getBatchForHour(8.999999)).toBe('morning');
|
||||
expect(getBatchForHour(16)).toBe('evening');
|
||||
expect(getBatchForHour(15.999999)).toBe('afternoon');
|
||||
});
|
||||
|
||||
it.each([-0.1, 24, 25, NaN])('should return null for the out-of-range hour %p', (hour) => {
|
||||
expect(getBatchForHour(hour)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRowBatchId', () => {
|
||||
it('should bucket on orderdate — the booking creation time', () => {
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T07:15:00' })).toBe('morning');
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T10:00:00' })).toBe('afternoon');
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T18:30:00' })).toBe('evening');
|
||||
});
|
||||
|
||||
it('should bucket the 2:43pm order that used to fall into the old 12:30-4pm black hole', () => {
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T14:43:00' })).toBe('afternoon');
|
||||
});
|
||||
|
||||
it('should use fractional hours so the back half of an hour is not mis-bucketed', () => {
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T15:45:00' })).toBe('afternoon');
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T16:01:00' })).toBe('evening');
|
||||
});
|
||||
|
||||
it('should strip a false trailing Z rather than shifting the row into another batch', () => {
|
||||
// 20:00 with a false Z read as a real UTC instant becomes 01:30 the next
|
||||
// day in IST, which would move an evening order into Morning.
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27T20:00:00Z' })).toBe('evening');
|
||||
});
|
||||
|
||||
it('should refuse to bucket a bare date, which has no time component', () => {
|
||||
// Midnight is a real morning-batch hour, so guessing would silently pile
|
||||
// every date-only row into Morning.
|
||||
expect(getRowBatchId({ orderdate: '2026-08-27' })).toBeNull();
|
||||
expect(getRowBatchId({ orderdate: ' 2026-08-27 ' })).toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['a row with no orderdate', {}],
|
||||
['a null orderdate', { orderdate: null }],
|
||||
['an empty orderdate', { orderdate: '' }],
|
||||
['a null row', null],
|
||||
['an undefined row', undefined]
|
||||
])('should return null for %s', (_label, row) => {
|
||||
expect(getRowBatchId(row)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for an unparseable orderdate rather than throwing', () => {
|
||||
expect(getRowBatchId({ orderdate: 'yesterday-ish' })).toBeNull();
|
||||
});
|
||||
|
||||
it('should ignore assigntime and expecteddeliverytime entirely', () => {
|
||||
// Both were tried and rejected: assigntime is re-stamped by any status
|
||||
// change, expecteddeliverytime describes the promised slot.
|
||||
const row = {
|
||||
orderdate: '2026-08-27T07:00:00',
|
||||
assigntime: '2026-08-27T18:00:00',
|
||||
expecteddeliverytime: '2026-08-27T16:30:00'
|
||||
};
|
||||
expect(getRowBatchId(row)).toBe('morning');
|
||||
});
|
||||
});
|
||||
|
||||
describe('labels', () => {
|
||||
it('should describe the morning and afternoon windows as ranges', () => {
|
||||
expect(batchRangeLabel(BATCHES[0])).toBe('12 AM–9 AM');
|
||||
expect(batchRangeLabel(BATCHES[1])).toBe('9 AM–4 PM');
|
||||
});
|
||||
|
||||
it('should describe a window that runs to end of day as "After X"', () => {
|
||||
expect(batchRangeLabel(BATCHES[2])).toBe('After 4 PM');
|
||||
});
|
||||
|
||||
it('should render a half-hour boundary with minutes', () => {
|
||||
expect(batchRangeLabel({ startHour: 12.5, endHour: 16 })).toBe('12:30 PM–4 PM');
|
||||
});
|
||||
|
||||
it('should return the human label for a known batch id', () => {
|
||||
expect(getBatchLabel('morning')).toBe('Morning Batch');
|
||||
expect(getBatchLabel('evening')).toBe('Evening Batch');
|
||||
});
|
||||
|
||||
it('should fall back to the id itself for an unknown batch', () => {
|
||||
expect(getBatchLabel('midnight')).toBe('midnight');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user