The real manual-send route (routes/outreach.mjs) always inserts with produto='' and lead_id=NULL, so the old single NOT EXISTS predicate (produto = ? AND (lead_id = leads.id OR numero = leads.whatsapp)) could never match a real manual send's row -- the numero branch was unreachable in production even though a prior test fabricated a produto value manual sends never write. Split into two NOT EXISTS clauses: one scoped to produto+lead_id (automatic/campaign sends), one scoped to numero+ origem='manual' with no produto requirement (manual sends). Updated the existing regression test to insert the row exactly like the real route does (produto: '', lead_id: NULL, origem: 'manual').
156 lines
6.5 KiB
JavaScript
156 lines
6.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { openDb, createLeadInserter } from '../src/db.mjs';
|
|
import { createCampaign } from '../src/outreach/campaign-store.mjs';
|
|
import { generateCampaignQueue, listQueue, approveQueueRows, cancelQueueRows } from '../src/outreach/campaign-queue.mjs';
|
|
|
|
function inserirLead(db, overrides = {}) {
|
|
const inserir = createLeadInserter(db);
|
|
const dedup = overrides.dedup_key || `lead-${Math.random()}`;
|
|
inserir({
|
|
dedup_key: dedup, empresa: 'Oficina Teste', legal_name: '', whatsapp: '5511999990001',
|
|
ddd: '11', email: '', cidade: 'SP', uf: 'SP', segmento: 'oficinas_mecanicas', segmento_label: '',
|
|
interesse: '', cnpj: '', cnae: '', endereco: '', website: '', pains_json: '[]', origem: 'teste',
|
|
telefone_valido: 1, cnpj_valido: 0, empresa_valida: 1, score: 80, classificacao: 'bom',
|
|
motivo_classificacao: '', score_qualidade: 80, score_comercial: 80, prioridade: 1,
|
|
produto_principal: 'ERP', produtos_json: '[]', capture_run_id: null, captured_at: null, exported_at: null,
|
|
...overrides
|
|
});
|
|
return db.prepare('SELECT * FROM leads WHERE dedup_key = ?').get(dedup);
|
|
}
|
|
|
|
function campanhaComVariantes(db, overrides = {}) {
|
|
return createCampaign(db, {
|
|
nome: 'ERP oficinas', produto: 'ERP', segmentos: ['oficinas_mecanicas'],
|
|
variantes: [{ code: 'A', mensagem: 'Ola {empresa}' }],
|
|
...overrides
|
|
});
|
|
}
|
|
|
|
test('generateCampaignQueue cria linhas preview so para leads do produto/segmento certos', () => {
|
|
const db = openDb(':memory:');
|
|
inserirLead(db, { dedup_key: 'l1', whatsapp: '5511999990001' });
|
|
inserirLead(db, { dedup_key: 'l2', segmento: 'outro_segmento', whatsapp: '5511999990002' });
|
|
const campanha = campanhaComVariantes(db);
|
|
|
|
const resultado = generateCampaignQueue(db, campanha.id);
|
|
assert.equal(resultado.candidatosEncontrados, 1);
|
|
assert.equal(resultado.adicionadosNaFila, 1);
|
|
|
|
const fila = listQueue(db, campanha.id);
|
|
assert.equal(fila.length, 1);
|
|
assert.equal(fila[0].status, 'preview');
|
|
assert.equal(fila[0].rendered_message, 'Ola Oficina Teste');
|
|
db.close();
|
|
});
|
|
|
|
test('generateCampaignQueue exclui lead com envio ja enviado para o mesmo produto (por lead_id)', () => {
|
|
const db = openDb(':memory:');
|
|
const lead = inserirLead(db);
|
|
db.prepare(
|
|
`INSERT INTO outreach_sends (lead_id, produto, numero, mensagem_enviada, status) VALUES (?, 'ERP', ?, 'oi', 'enviado')`
|
|
).run(lead.id, lead.whatsapp);
|
|
const campanha = campanhaComVariantes(db);
|
|
|
|
const resultado = generateCampaignQueue(db, campanha.id);
|
|
assert.equal(resultado.candidatosEncontrados, 0);
|
|
db.close();
|
|
});
|
|
|
|
test('generateCampaignQueue exclui numero com envio manual ja enviado (sem lead_id, produto vazio, como o envio manual real grava)', () => {
|
|
const db = openDb(':memory:');
|
|
inserirLead(db, { whatsapp: '5511999990009' });
|
|
// Reproduz exatamente como src/server/routes/outreach.mjs grava um envio
|
|
// manual: lead_id NULL, produto '' (nunca o produto da campanha) e
|
|
// origem='manual'. A exclusao precisa valer mesmo assim, por numero.
|
|
db.prepare(
|
|
`INSERT INTO outreach_sends (lead_id, produto, numero, mensagem_enviada, status, origem)
|
|
VALUES (NULL, '', '5511999990009', 'oi', 'enviado', 'manual')`
|
|
).run();
|
|
const campanha = campanhaComVariantes(db);
|
|
|
|
const resultado = generateCampaignQueue(db, campanha.id);
|
|
assert.equal(resultado.candidatosEncontrados, 0);
|
|
db.close();
|
|
});
|
|
|
|
test('generateCampaignQueue exclui numero em opt-out', () => {
|
|
const db = openDb(':memory:');
|
|
inserirLead(db, { whatsapp: '5511999990001' });
|
|
db.prepare(`INSERT INTO outreach_opt_out (numero) VALUES ('5511999990001')`).run();
|
|
const campanha = campanhaComVariantes(db);
|
|
|
|
const resultado = generateCampaignQueue(db, campanha.id);
|
|
assert.equal(resultado.candidatosEncontrados, 0);
|
|
db.close();
|
|
});
|
|
|
|
test('generateCampaignQueue nao duplica lead ja na fila (preview/approved/sent) de outra campanha do mesmo produto', () => {
|
|
const db = openDb(':memory:');
|
|
inserirLead(db, { dedup_key: 'unico', whatsapp: '5511999990001' });
|
|
const campanha1 = campanhaComVariantes(db, { nome: 'Campanha 1' });
|
|
generateCampaignQueue(db, campanha1.id);
|
|
|
|
const campanha2 = campanhaComVariantes(db, { nome: 'Campanha 2' });
|
|
const resultado = generateCampaignQueue(db, campanha2.id);
|
|
assert.equal(resultado.candidatosEncontrados, 0);
|
|
db.close();
|
|
});
|
|
|
|
test('generateCampaignQueue respeita o parametro quantidade', () => {
|
|
const db = openDb(':memory:');
|
|
for (let i = 0; i < 5; i += 1) {
|
|
inserirLead(db, { dedup_key: `lead-${i}`, whatsapp: `551199999000${i}` });
|
|
}
|
|
const campanha = campanhaComVariantes(db);
|
|
const resultado = generateCampaignQueue(db, campanha.id, { quantidade: 3 });
|
|
assert.equal(resultado.adicionadosNaFila, 3);
|
|
db.close();
|
|
});
|
|
|
|
test('generateCampaignQueue reporta candidatosEncontrados como o pool elegivel completo, mesmo com quantidade menor', () => {
|
|
const db = openDb(':memory:');
|
|
for (let i = 0; i < 5; i += 1) {
|
|
inserirLead(db, { dedup_key: `pool-${i}`, whatsapp: `551199998000${i}` });
|
|
}
|
|
const campanha = campanhaComVariantes(db);
|
|
const resultado = generateCampaignQueue(db, campanha.id, { quantidade: 2 });
|
|
assert.equal(resultado.candidatosEncontrados, 5);
|
|
assert.equal(resultado.adicionadosNaFila, 2);
|
|
assert.equal(listQueue(db, campanha.id).length, 2);
|
|
db.close();
|
|
});
|
|
|
|
test('approveQueueRows so aprova linhas em preview da campanha certa', () => {
|
|
const db = openDb(':memory:');
|
|
inserirLead(db, { whatsapp: '5511999990001' });
|
|
const campanha = campanhaComVariantes(db);
|
|
generateCampaignQueue(db, campanha.id);
|
|
const [linha] = listQueue(db, campanha.id);
|
|
|
|
const total = approveQueueRows(db, campanha.id, [linha.id]);
|
|
assert.equal(total, 1);
|
|
assert.equal(listQueue(db, campanha.id, 'approved').length, 1);
|
|
});
|
|
|
|
test('cancelQueueRows cancela e registra motivo', () => {
|
|
const db = openDb(':memory:');
|
|
inserirLead(db, { whatsapp: '5511999990001' });
|
|
const campanha = campanhaComVariantes(db);
|
|
generateCampaignQueue(db, campanha.id);
|
|
const [linha] = listQueue(db, campanha.id);
|
|
|
|
const total = cancelQueueRows(db, campanha.id, [linha.id], 'nao interessa');
|
|
assert.equal(total, 1);
|
|
const cancelada = listQueue(db, campanha.id, 'cancelled')[0];
|
|
assert.equal(cancelada.cancel_reason, 'nao interessa');
|
|
});
|
|
|
|
test('approveQueueRows/cancelQueueRows com lista vazia nao lanca erro', () => {
|
|
const db = openDb(':memory:');
|
|
const campanha = campanhaComVariantes(db);
|
|
assert.equal(approveQueueRows(db, campanha.id, []), 0);
|
|
assert.equal(cancelQueueRows(db, campanha.id, []), 0);
|
|
db.close();
|
|
});
|