Files
COMERCIAL-GTI/tests/outreach-routes.test.mjs
T
Gustavo 4f736562f3 Carry over pre-existing WABA outreach automation WIP
This work already existed uncommitted in the main working tree (not
authored by this branch) and is required as a runtime dependency for the
campaign outreach port (zpro-client, templates, outreach routes/UI). Copied
in as-is, untouched, so this feature branch has a working baseline; the
original uncommitted state in the main checkout is left exactly as it was.
2026-08-01 10:12:25 -03:00

254 lines
8.9 KiB
JavaScript

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();
});