Files
COMERCIAL-GTI/tests/campaign-schedule.test.mjs
T
Gustavo d330bb5ff0 Baseline commit of the existing Maquina Comercial Pessoal project
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.
2026-07-31 10:54:07 -03:00

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);
});