Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: maquina-comercial-pessoal git-subtree-mainline:f5af038e19git-subtree-split:a9ef4d776d
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();
|
|
});
|