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 { runDueCampaignQueue } from '../src/outreach/campaign-worker.mjs'; import { recordInboundReply } from '../src/outreach/inbound.mjs'; function setup() { const db = openDb(':memory:'); seedCampaigns(db); const campaigns = db.prepare('SELECT * FROM outreach_campaigns ORDER BY id').all(); return { db, campaigns }; } function addLead(db, suffix, numero) { return Number(db.prepare( `INSERT INTO leads (dedup_key, empresa, whatsapp, segmento, telefone_valido) VALUES (?, ?, ?, 'restaurantes', 1)` ).run(`inbound-${suffix}`, `Empresa ${suffix}`, numero).lastInsertRowid); } function addQueue(db, campaign, { leadId, numero, suffix, status = 'preview' }) { const variantId = db.prepare( 'SELECT id FROM outreach_campaign_variants WHERE campaign_id = ? ORDER BY id LIMIT 1' ).get(campaign.id).id; const queueId = Number(db.prepare( `INSERT INTO outreach_campaign_queue (campaign_id, lead_id, variant_id, produto, numero, segmento, rendered_message, selection_reason, status) VALUES (?, ?, ?, ?, ?, 'restaurantes', ?, 'inbound-test', 'preview')` ).run(campaign.id, leadId, variantId, campaign.produto, numero, `Mensagem ${suffix}`).lastInsertRowid); if (status === 'approved') { db.prepare("UPDATE outreach_campaign_queue SET status = 'approved' WHERE id = ?").run(queueId); } else if (status === 'scheduled') { db.prepare( "UPDATE outreach_campaign_queue SET status = 'scheduled', scheduled_at = '2026-07-29T12:00:00.000Z' WHERE id = ?" ).run(queueId); } return queueId; } function addHistoricalSend(db, campaign, { leadId, numero, suffix, sentAt }) { const queueId = addQueue(db, campaign, { leadId, numero, suffix, status: 'scheduled' }); const sendId = Number(db.prepare( `INSERT INTO outreach_sends (lead_id, produto, numero, mensagem_enviada, status, origem, sent_at, provider_message_id) VALUES (?, ?, ?, ?, 'aceito', 'automatico', ?, ?)` ).run(leadId, campaign.produto, numero, `Mensagem ${suffix}`, sentAt, `outbound-${suffix}`).lastInsertRowid); db.prepare( "UPDATE outreach_campaign_queue SET status = 'reserved', outreach_send_id = ? WHERE id = ?" ).run(sendId, queueId); db.prepare("UPDATE outreach_campaign_queue SET status = 'sent' WHERE id = ?").run(queueId); return queueId; } test('inbound usa envio de campanha mais recente entre variantes e aplica pausa idempotente', () => { const { db, campaigns } = setup(); const withNine = '5575999091780'; const withoutNine = '557599091780'; const oldLeadId = addLead(db, 'old', withNine); const latestLeadId = addLead(db, 'latest', withoutNine); addHistoricalSend(db, campaigns[0], { leadId: oldLeadId, numero: withNine, suffix: 'old', sentAt: '2026-07-29T10:00:00.000Z' }); addHistoricalSend(db, campaigns[1], { leadId: latestLeadId, numero: withoutNine, suffix: 'latest', sentAt: '2026-07-29T11:00:00.000Z' }); db.prepare( `INSERT INTO outreach_lead_states (lead_id, stage, first_reply_at, updated_at) VALUES (?, 'interessado_gti_vendas', '2026-07-20T09:00:00.000Z', '2026-07-20T09:00:00.000Z')` ).run(latestLeadId); const mutable = ['preview', 'approved', 'scheduled'].map((status, index) => { const leadId = addLead(db, `mutable-${status}`, index % 2 ? withoutNine : withNine); return addQueue(db, campaigns[index], { leadId, numero: index % 2 ? withoutNine : withNine, suffix: status, status }); }); const event = { messageId: 'provider-inbound-1', jid: `${withNine}@s.whatsapp.net`, number: withNine, receivedAt: '2026-07-29T12:00:00.000Z' }; assert.deepEqual(recordInboundReply(db, event), { inserted: true, leadId: latestLeadId, cancelled: 3 }); assert.deepEqual(recordInboundReply(db, event), { inserted: false, leadId: latestLeadId, cancelled: 0 }); assert.deepEqual( { ...db.prepare('SELECT lead_id, numero, received_at FROM outreach_inbound_replies').get() }, { lead_id: latestLeadId, numero: withNine, received_at: event.receivedAt } ); assert.deepEqual( { ...db.prepare('SELECT stage, first_reply_at, updated_at FROM outreach_lead_states WHERE lead_id = ?').get(latestLeadId) }, { stage: 'respondeu', first_reply_at: '2026-07-20T09:00:00.000Z', updated_at: event.receivedAt } ); assert.deepEqual( db.prepare('SELECT lead_id, campaign_id, stage, created_at FROM outreach_stage_events').all().map((row) => ({ ...row })), [{ lead_id: latestLeadId, campaign_id: campaigns[1].id, stage: 'respondeu', created_at: event.receivedAt }] ); assert.deepEqual( db.prepare(`SELECT status, cancel_reason FROM outreach_campaign_queue WHERE id IN (${mutable.map(() => '?').join(',')}) ORDER BY id`).all(...mutable).map((row) => ({ ...row })), mutable.map(() => ({ status: 'cancelled', cancel_reason: 'inbound_reply' })) ); assert.equal(db.prepare('SELECT COUNT(*) AS total FROM outreach_opt_outs').get().total, 0); assert.equal(db.prepare("SELECT COUNT(*) AS total FROM outreach_lead_states WHERE stage <> 'respondeu' AND lead_id = ?").get(latestLeadId).total, 0); db.close(); }); test('inbound vence corrida no beforeSend e impede chamada ao provedor', async () => { const { db, campaigns } = setup(); const campaign = campaigns[0]; db.prepare("UPDATE outreach_campaigns SET status = 'running' WHERE id = ?").run(campaign.id); const numero = '5575999091780'; const leadId = addLead(db, 'race', numero); saveWhatsAppVerification(db, { numero, status: 'confirmado' }); const queueId = addQueue(db, campaign, { leadId, numero, suffix: 'race', status: 'scheduled' }); let releaseGuard; let guardStarted; const blocked = new Promise((resolve) => { guardStarted = resolve; }); const gate = new Promise((resolve) => { releaseGuard = resolve; }); let providerCalls = 0; const whatsapp = { getStatus: () => ({ state: 'READY' }), sendText: async (args) => { const originalBeforeSend = args.beforeSend; args.beforeSend = async () => { guardStarted(); await gate; return originalBeforeSend(); }; const context = await args.beforeSend(); providerCalls += 1; const result = { messageId: 'must-not-send' }; await args.afterSend(result, null, context); return result; } }; const running = runDueCampaignQueue({ db, whatsapp, now: new Date('2026-07-29T12:00:00.000Z'), log: () => {} }); await blocked; assert.deepEqual(recordInboundReply(db, { messageId: 'provider-race-1', jid: `${numero}@s.whatsapp.net`, number: numero, receivedAt: '2026-07-29T11:59:59.000Z' }), { inserted: true, leadId: null, cancelled: 1 }); releaseGuard(); const summary = await running; assert.equal(providerCalls, 0); assert.equal(summary.enviados, 0); assert.deepEqual( { ...db.prepare('SELECT status, cancel_reason FROM outreach_campaign_queue WHERE id = ?').get(queueId) }, { status: 'cancelled', cancel_reason: 'inbound_reply' } ); assert.equal(db.prepare('SELECT COUNT(*) AS total FROM outreach_sends').get().total, 0); db.close(); });