import test from 'node:test'; import assert from 'node:assert/strict'; import { openDb, setSetting } from '../src/db.mjs'; import { createRouter } from '../src/server/router.mjs'; import { registerOutreachRoutes } from '../src/server/routes/outreach.mjs'; function setupRouter(db) { const router = createRouter(); registerOutreachRoutes(router, { db }); return router; } function fakeJson() { const calls = []; const json = (status, data) => calls.push({ status, data }); return { json, calls }; } function configurarCredenciais(db) { setSetting(db, 'zpro.baseUrl', 'exemplo.zpro.com.br'); setSetting(db, 'zpro.apiId', 'abc123'); setSetting(db, 'zpro.bearerToken', 'token-teste'); } test('POST /api/outreach/enviar-manual exige numero', async () => { const db = openDb(':memory:'); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); await handler(null, null, { json, body: { numero: '', mensagem: 'oi' } }); assert.equal(calls[0].status, 400); db.close(); }); test('POST /api/outreach/enviar-manual rejeita numero mal formatado antes de chamar o Z-PRO', async () => { const db = openDb(':memory:'); configurarCredenciais(db); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); const originalFetch = global.fetch; let fetchChamado = false; global.fetch = async () => { fetchChamado = true; return { ok: true, text: async () => '{}' }; }; try { await handler(null, null, { json, body: { numero: '55759990917800', canal: 'nao_oficial', mensagem: 'oi' } }); } finally { global.fetch = originalFetch; } assert.equal(calls[0].status, 400); assert.match(calls[0].data.erro, /numero invalido/); assert.equal(fetchChamado, false); assert.equal(db.prepare('SELECT COUNT(*) c FROM outreach_sends').get().c, 0); db.close(); }); test('POST /api/outreach/enviar-manual manda texto livre (nao-oficial) e registra origem=manual', async () => { const db = openDb(':memory:'); configurarCredenciais(db); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); const originalFetch = global.fetch; global.fetch = async () => ({ ok: true, text: async () => JSON.stringify({ data: { ticketId: 999 } }) }); try { await handler(null, null, { json, body: { numero: '5511999990001', canal: 'nao_oficial', mensagem: 'Oi, tudo bem?' } }); } finally { global.fetch = originalFetch; } assert.deepEqual(calls[0], { status: 200, data: { ok: true, ticketId: '999' } }); const row = db.prepare('SELECT * FROM outreach_sends').get(); assert.equal(row.origem, 'manual'); assert.equal(row.lead_id, null); assert.equal(row.numero, '5511999990001'); assert.equal(row.mensagem_enviada, 'Oi, tudo bem?'); assert.equal(row.canal, 'nao_oficial'); assert.equal(row.zpro_ticket_id, '999'); db.close(); }); test('POST /api/outreach/enviar-manual bloqueia quando limite diario acabou', async () => { const db = openDb(':memory:'); configurarCredenciais(db); setSetting(db, 'outreach.mensagensPorDia', '1'); db.prepare( `INSERT INTO outreach_sends (lead_id, produto, numero, mensagem_enviada, status, canal, origem) VALUES (NULL, '', '5511999990001', 'ja enviado', 'enviado', 'nao_oficial', 'manual')` ).run(); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); const originalFetch = global.fetch; let fetchChamado = false; global.fetch = async () => { fetchChamado = true; return { ok: true, text: async () => '{}' }; }; try { await handler(null, null, { json, body: { numero: '5511999990002', canal: 'nao_oficial', mensagem: 'Oi' } }); } finally { global.fetch = originalFetch; } assert.equal(calls[0].status, 429); assert.equal(calls[0].data.ok, false); assert.match(calls[0].data.erro, /limite diario atingido/); assert.equal(fetchChamado, false); assert.equal(db.prepare('SELECT COUNT(*) c FROM outreach_sends').get().c, 1); db.close(); }); test('POST /api/outreach/enviar-manual canal oficial exige nome do template', async () => { const db = openDb(':memory:'); configurarCredenciais(db); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); await handler(null, null, { json, body: { numero: '5511999990001', canal: 'oficial' } }); assert.equal(calls[0].status, 400); db.close(); }); test('POST /api/outreach/enviar-manual canal oficial envia multiplas variaveis do template', async () => { const db = openDb(':memory:'); configurarCredenciais(db); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); const originalFetch = global.fetch; let payload; global.fetch = async (url, options) => { payload = JSON.parse(options.body); return { ok: true, status: 200, text: async () => '{"success":true,"data":{"ticketId":321}}' }; }; try { await handler(null, null, { json, body: { numero: '5511999990001', canal: 'oficial', templateWabaNome: 'gti_inicio_atendimento_solicitado', templateWabaIdioma: 'pt_BR', templateVariaveis: ['Gustavo', 'Chamado #123', ''] } }); } finally { global.fetch = originalFetch; } assert.deepEqual(calls[0], { status: 200, data: { ok: true, ticketId: '321' } }); assert.deepEqual(payload.templateData.template.components, [ { type: 'body', parameters: [{ type: 'text', text: 'Gustavo' }, { type: 'text', text: 'Chamado #123' }] } ]); db.close(); }); test('POST /api/outreach/enviar-manual resolve variaveis internas do GChat', async () => { const db = openDb(':memory:'); configurarCredenciais(db); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json } = fakeJson(); const originalFetch = global.fetch; let payload; global.fetch = async (url, options) => { payload = JSON.parse(options.body); return { ok: true, status: 200, text: async () => '{"success":true,"data":{"ticketId":321}}' }; }; try { await handler(null, null, { json, body: { numero: '5511999990001', canal: 'oficial', templateWabaNome: 'gti_teste', templateWabaIdioma: 'pt_BR', templateVariaveis: ['{{name}}', '{{protocol}}', '{{phoneNumber}}'] } }); } finally { global.fetch = originalFetch; } assert.deepEqual(payload.templateData.template.components, [ { type: 'body', parameters: [ { type: 'text', text: 'Gustavo' }, { type: 'text', text: 'Atendimento comercial' }, { type: 'text', text: '5511999990001' } ] } ]); db.close(); }); test('POST /api/outreach/enviar-manual registra erro quando o Z-PRO falha', async () => { const db = openDb(':memory:'); configurarCredenciais(db); const router = setupRouter(db); const { handler } = router.match('POST', '/api/outreach/enviar-manual'); const { json, calls } = fakeJson(); const originalFetch = global.fetch; global.fetch = async () => ({ ok: false, status: 400, text: async () => '{"erro":"numero nao existe no whatsapp"}' }); try { await handler(null, null, { json, body: { numero: '5511999990001', canal: 'nao_oficial', mensagem: 'teste' } }); } finally { global.fetch = originalFetch; } assert.equal(calls[0].data.ok, false); const row = db.prepare('SELECT * FROM outreach_sends').get(); assert.equal(row.status, 'erro'); assert.match(row.erro, /numero nao existe/); db.close(); }); test('GET /api/outreach/status inclui envios manuais em "ultimos" (LEFT JOIN, sem lead)', async () => { const db = openDb(':memory:'); db.prepare( `INSERT INTO outreach_sends (lead_id, produto, numero, mensagem_enviada, status, canal, origem) VALUES (NULL, '', '5511999990001', 'oi', 'enviado', 'nao_oficial', 'manual')` ).run(); const router = setupRouter(db); const { handler } = router.match('GET', '/api/outreach/status'); const { json, calls } = fakeJson(); await handler(null, null, { json }); const ultimo = calls[0].data.ultimos[0]; assert.equal(ultimo.origem, 'manual'); assert.equal(ultimo.numero, '5511999990001'); db.close(); }); test('GET /api/outreach/variables lista variaveis comerciais', async () => { const db = openDb(':memory:'); const router = setupRouter(db); const { handler } = router.match('GET', '/api/outreach/variables'); const { json, calls } = fakeJson(); await handler(null, null, { json }); assert.equal(calls[0].status, 200); assert.ok(calls[0].data.variables.some((variable) => variable.key === 'empresa')); assert.ok(calls[0].data.variables.some((variable) => variable.key === 'name')); db.close(); });