Tracks the app that was already in place (capture, treatment, outreach, WhatsApp integration, server, frontend, tests, docs) so future feature work can branch and use isolated worktrees.
88 lines
3.0 KiB
JavaScript
88 lines
3.0 KiB
JavaScript
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);
|
|
});
|