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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user