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.
147 lines
5.2 KiB
JavaScript
147 lines
5.2 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { openDb, saveWhatsAppVerification } from '../src/db.mjs';
|
|
import { seedCampaigns } from '../src/outreach/campaign-store.mjs';
|
|
import { runOutreachBatch } from '../src/outreach/run.mjs';
|
|
import { renderTemplate } from '../src/outreach/templates.mjs';
|
|
|
|
function addScheduledQueue(db, scheduledAt = '2026-07-29T12:00:00.000Z') {
|
|
seedCampaigns(db);
|
|
const campaign = db.prepare('SELECT * FROM outreach_campaigns ORDER BY id LIMIT 1').get();
|
|
db.prepare("UPDATE outreach_campaigns SET status = 'running' WHERE id = ?").run(campaign.id);
|
|
const leadId = Number(db.prepare(
|
|
`INSERT INTO leads (dedup_key, empresa, whatsapp, segmento, telefone_valido)
|
|
VALUES ('queue-lead', 'Empresa Fila', '5511999990001', 'restaurantes', 1)`
|
|
).run().lastInsertRowid);
|
|
saveWhatsAppVerification(db, { numero: '5511999990001', status: 'confirmado' });
|
|
const variantId = db.prepare(
|
|
'SELECT id FROM outreach_campaign_variants WHERE campaign_id = ? ORDER BY id LIMIT 1'
|
|
).get(campaign.id).id;
|
|
return Number(db.prepare(
|
|
`INSERT INTO outreach_campaign_queue
|
|
(campaign_id, lead_id, variant_id, produto, numero, segmento,
|
|
rendered_message, selection_reason, status, scheduled_at)
|
|
VALUES (?, ?, ?, ?, '5511999990001', 'restaurantes',
|
|
'Mensagem aprovada e imutavel', 'teste', 'scheduled', ?)`
|
|
).run(campaign.id, leadId, variantId, campaign.produto, scheduledAt).lastInsertRowid);
|
|
}
|
|
|
|
function whatsappFake() {
|
|
const sent = [];
|
|
return {
|
|
sent,
|
|
getStatus: () => ({ state: 'READY' }),
|
|
sendText: async (args) => {
|
|
const context = await args.beforeSend();
|
|
sent.push({ number: args.number, message: args.message });
|
|
const result = { messageId: 'queue-message-1' };
|
|
await args.afterSend(result, null, context);
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
|
|
test('renderTemplate inclui produto no contexto pessoal', () => {
|
|
assert.equal(renderTemplate('{empresa}: {produto}', { empresa: 'Loja', produto: 'ERP' }), 'Loja: ERP');
|
|
});
|
|
|
|
test('runOutreachBatch consome somente uma linha aprovada e vencida da fila', async () => {
|
|
const db = openDb(':memory:');
|
|
const queueId = addScheduledQueue(db);
|
|
const whatsapp = whatsappFake();
|
|
|
|
const result = await runOutreachBatch({
|
|
db,
|
|
whatsapp,
|
|
now: new Date('2026-07-29T12:00:00.000Z'),
|
|
log: () => {}
|
|
});
|
|
|
|
assert.deepEqual(result, { tentados: 1, enviados: 1, erros: 0, pulados: 0 });
|
|
assert.deepEqual(whatsapp.sent, [{
|
|
number: '5511999990001',
|
|
message: 'Mensagem aprovada e imutavel'
|
|
}]);
|
|
assert.equal(db.prepare('SELECT status FROM outreach_campaign_queue WHERE id = ?').get(queueId).status, 'sent');
|
|
assert.deepEqual(
|
|
{ ...db.prepare(
|
|
'SELECT mensagem_enviada, provider_message_id, status, canal, origem FROM outreach_sends'
|
|
).get() },
|
|
{
|
|
mensagem_enviada: 'Mensagem aprovada e imutavel',
|
|
provider_message_id: 'queue-message-1',
|
|
status: 'aceito',
|
|
canal: 'local',
|
|
origem: 'automatico'
|
|
}
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test('runOutreachBatch sem WhatsApp READY preserva fila e registra adiamento', async () => {
|
|
const db = openDb(':memory:');
|
|
const queueId = addScheduledQueue(db);
|
|
const logs = [];
|
|
let sends = 0;
|
|
|
|
const result = await runOutreachBatch({
|
|
db,
|
|
whatsapp: {
|
|
getStatus: () => ({ state: 'DISCONNECTED' }),
|
|
sendText: async () => { sends += 1; }
|
|
},
|
|
now: new Date('2026-07-29T12:00:00.000Z'),
|
|
log: (line) => logs.push(line)
|
|
});
|
|
|
|
assert.deepEqual(result, { tentados: 0, enviados: 0, erros: 0, pulados: 0 });
|
|
assert.equal(sends, 0);
|
|
assert.equal(db.prepare('SELECT status FROM outreach_campaign_queue WHERE id = ?').get(queueId).status, 'scheduled');
|
|
assert.match(logs[0], /nao esta conectado/i);
|
|
db.close();
|
|
});
|
|
|
|
test('runOutreachBatch nunca seleciona lead cru sem fila supervisionada', async () => {
|
|
const db = openDb(':memory:');
|
|
db.prepare(
|
|
`INSERT INTO leads
|
|
(dedup_key, empresa, whatsapp, segmento, telefone_valido, produto_principal)
|
|
VALUES ('raw-lead', 'Lead Cru', '5511999990002', 'restaurantes', 1, 'G-Delivery')`
|
|
).run();
|
|
saveWhatsAppVerification(db, { numero: '5511999990002', status: 'confirmado' });
|
|
const whatsapp = whatsappFake();
|
|
|
|
const result = await runOutreachBatch({
|
|
db,
|
|
whatsapp,
|
|
now: new Date('2026-07-29T12:00:00.000Z'),
|
|
log: () => {}
|
|
});
|
|
|
|
assert.deepEqual(result, { tentados: 0, enviados: 0, erros: 0, pulados: 0 });
|
|
assert.equal(whatsapp.sent.length, 0);
|
|
assert.equal(db.prepare('SELECT COUNT(*) AS total FROM outreach_sends').get().total, 0);
|
|
db.close();
|
|
});
|
|
|
|
test('runOutreachBatch respeita cancelamento antes da reserva', async () => {
|
|
const db = openDb(':memory:');
|
|
const queueId = addScheduledQueue(db);
|
|
const whatsapp = whatsappFake();
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
|
|
const result = await runOutreachBatch({
|
|
db,
|
|
whatsapp,
|
|
now: new Date('2026-07-29T12:00:00.000Z'),
|
|
signal: controller.signal,
|
|
log: () => {}
|
|
});
|
|
|
|
assert.deepEqual(result, { tentados: 0, enviados: 0, erros: 0, pulados: 0 });
|
|
assert.equal(db.prepare('SELECT status FROM outreach_campaign_queue WHERE id = ?').get(queueId).status, 'scheduled');
|
|
assert.equal(whatsapp.sent.length, 0);
|
|
db.close();
|
|
});
|