import test from 'node:test'; import assert from 'node:assert/strict'; import { generateDailySchedule } from '../src/outreach/campaign-schedule.mjs'; const WINDOWS = [ { start: '09:30', end: '11:30' }, { start: '14:00', end: '17:00' } ]; const MIN_MS = 12 * 60 * 1000; const MAX_MS = 25 * 60 * 1000; function schedule(overrides = {}) { return generateDailySchedule({ date: '2026-07-27', count: 18, windows: WINDOWS, minMs: MIN_MS, maxMs: MAX_MS, hourlyLimit: 4, random: () => 0, ...overrides }); } test('gera somente em dia util e preserva a data civil local', () => { assert.deepEqual(schedule({ date: '2026-07-25' }), []); assert.deepEqual(schedule({ date: '2026-07-26' }), []); const slots = schedule({ date: '2026-07-27', count: 2 }); assert.equal(slots.length, 2); assert.deepEqual(slots.map((slot) => [ slot.getFullYear(), slot.getMonth() + 1, slot.getDate(), slot.getHours(), slot.getMinutes() ]), [ [2026, 7, 27, 9, 30], [2026, 7, 27, 9, 42] ]); }); test('usa random injetado para espacamento inclusivo entre 12 e 25 minutos', () => { const minimum = schedule({ count: 3, random: () => 0 }); assert.deepEqual(minimum.map((slot) => `${slot.getHours()}:${String(slot.getMinutes()).padStart(2, '0')}`), [ '9:30', '9:42', '9:54' ]); const maximum = schedule({ count: 3, random: () => 1 }); assert.deepEqual(maximum.map((slot) => `${slot.getHours()}:${String(slot.getMinutes()).padStart(2, '0')}`), [ '9:30', '9:55', '10:20' ]); }); test('respeita no maximo quatro slots em qualquer hora rolante, incluindo o limite exato', () => { const slots = schedule({ count: 8, minMs: 1, maxMs: 1 }); assert.deepEqual(slots.slice(0, 5).map((slot) => slot.getTime() - slots[0].getTime()), [ 0, 1, 2, 3, 60 * 60 * 1000 ]); for (let index = 4; index < slots.length; index += 1) { assert.ok(slots[index].getTime() - slots[index - 4].getTime() >= 60 * 60 * 1000); } }); test('nao agenda durante almoco, fora das janelas ou em outra data', () => { const slots = schedule({ count: 18 }); assert.ok(slots.length > 0); for (const slot of slots) { const minutes = slot.getHours() * 60 + slot.getMinutes(); assert.equal(slot.getDate(), 27); assert.ok((minutes >= 570 && minutes <= 690) || (minutes >= 840 && minutes <= 1020)); assert.ok(minutes <= 690 || minutes >= 840); } }); test('limita a 18 e retorna menos quando as janelas nao comportam a quantidade', () => { assert.equal(schedule({ count: 99 }).length, 18); const short = schedule({ count: 18, windows: [{ start: '09:30', end: '09:50' }], minMs: 12 * 60 * 1000, maxMs: 12 * 60 * 1000 }); assert.deepEqual(short.map((slot) => [slot.getHours(), slot.getMinutes()]), [[9, 30], [9, 42]]); }); test('rejeita parametros inseguros sem produzir datas invalidas', () => { assert.throws(() => schedule({ date: '2026-02-30' }), /data/i); assert.throws(() => schedule({ windows: [{ start: '11:30', end: '09:30' }] }), /janela/i); assert.throws(() => schedule({ random: () => 1.1 }), /random/i); });