Baseline commit of the existing Maquina Comercial Pessoal project
Tracks the app that was already in place (capture, treatment, outreach, WhatsApp integration, server, frontend, tests, docs) so future feature work can branch and use isolated worktrees.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { openDb, setSetting } from '../../src/db.mjs';
|
||||
import { hashPassword } from '../../src/auth.mjs';
|
||||
import { createUser } from '../../src/db.mjs';
|
||||
import { createAppServer } from '../../src/server/index.mjs';
|
||||
import { seedCampaigns } from '../../src/outreach/campaign-store.mjs';
|
||||
|
||||
const DEBUG = process.env.DEBUG_SERVER === '1';
|
||||
const log = DEBUG ? (m) => process.stderr.write(`[test-server] ${m}\n`) : () => {};
|
||||
|
||||
function randomHex(length) {
|
||||
const bytes = new Uint8Array(Math.ceil(length / 2));
|
||||
crypto.getRandomValues(bytes);
|
||||
return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('').slice(0, length);
|
||||
}
|
||||
|
||||
export async function startTestServer() {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'));
|
||||
const dbPath = path.join(tmpDir, 'test.db');
|
||||
|
||||
process.env.APP_DB_PATH = dbPath;
|
||||
process.env.APP_WHATSAPP_AUTH_DIR = path.join(tmpDir, 'whatsapp-auth');
|
||||
|
||||
const db = openDb(dbPath);
|
||||
|
||||
const adminPw = 'admin-senha-teste';
|
||||
createUser(db, { username: 'admin', passwordHash: hashPassword(adminPw), role: 'admin' });
|
||||
|
||||
seedCampaigns(db);
|
||||
setSetting(db, 'outreach.mensagensPorDia', '10');
|
||||
|
||||
const campaignIds = db.prepare('SELECT id, code FROM outreach_campaigns ORDER BY id').all();
|
||||
const leadIds = [];
|
||||
const empresas = {
|
||||
'1': ['Hamburgueria Bella', 'Pizzaria Famosa', 'Acaiteria Tropical', 'Marmitaria Sabor', 'Lanchonete do Ze'],
|
||||
'2': ['Restaurante Chefe', 'Mercado Central', 'Mercearia Sao Jose', 'Minimercado Bom', 'Padaria Pao Quente'],
|
||||
'3': ['Loja de Moda', 'Material Construcao', 'Autopecas Veloz', 'Oficina Mecanica']
|
||||
};
|
||||
|
||||
let leadIdCounter = 1;
|
||||
for (const c of campaignIds) {
|
||||
const names = empresas[c.code] || ['Empresa Generica'];
|
||||
for (const nome of names) {
|
||||
const whatsapp = `5511999${String(leadIdCounter).padStart(6, '0')}`;
|
||||
const r = db.prepare(
|
||||
`INSERT INTO leads (dedup_key, empresa, whatsapp, segmento, prioridade, score, classificacao, telefone_valido)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'bom', 1)`
|
||||
).run(
|
||||
`browser-test-lead-${leadIdCounter}`,
|
||||
nome,
|
||||
whatsapp,
|
||||
c.code === 'gdelivery-food' ? 'hamburguerias' : c.code === 'gti-food' ? 'restaurantes' : 'lojas_de_roupa',
|
||||
leadIdCounter <= 5 ? 1 : 2,
|
||||
100 - leadIdCounter
|
||||
);
|
||||
const lid = Number(r.lastInsertRowid);
|
||||
leadIds.push({ id: lid, empresa: nome, whatsapp, campaignId: c.id });
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO whatsapp_number_verifications (numero, status, numero_resolvido, checked_at)
|
||||
VALUES (?, 'confirmado', ?, datetime('now'))`
|
||||
).run(whatsapp, whatsapp);
|
||||
|
||||
leadIdCounter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const firstLead = leadIds[0];
|
||||
setSetting(db, 'outreach.ativo', 'true');
|
||||
|
||||
const firstCampaignId = campaignIds[0].id;
|
||||
const variantIds = db.prepare(
|
||||
'SELECT id FROM outreach_campaign_variants WHERE campaign_id = ? ORDER BY code LIMIT 1'
|
||||
).all(firstCampaignId);
|
||||
if (variantIds.length > 0 && firstLead) {
|
||||
const variantId = variantIds[0].id;
|
||||
const produto = db.prepare('SELECT produto FROM outreach_campaigns WHERE id = ?').get(firstCampaignId).produto;
|
||||
db.prepare(
|
||||
`INSERT INTO outreach_campaign_queue
|
||||
(campaign_id, lead_id, variant_id, produto, numero, segmento, rendered_message, selection_reason, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'preview')`
|
||||
).run(firstCampaignId, firstLead.id, variantId, produto, firstLead.whatsapp, 'hamburguerias',
|
||||
'Ola! Teste de mensagem renderizada para campanha.', 'browser-test');
|
||||
}
|
||||
|
||||
const stageLeadId = leadIds.length > 2 ? leadIds[2].id : (firstLead ? firstLead.id : null);
|
||||
if (stageLeadId) {
|
||||
db.prepare(
|
||||
`INSERT INTO outreach_lead_states (lead_id, stage, first_reply_at)
|
||||
VALUES (?, 'respondeu', datetime('now'))`
|
||||
).run(stageLeadId);
|
||||
db.prepare(
|
||||
`INSERT INTO outreach_stage_events (lead_id, campaign_id, stage, note)
|
||||
VALUES (?, ?, 'respondeu', 'cliente quer saber mais sobre G-Delivery')`
|
||||
).run(stageLeadId, firstCampaignId);
|
||||
}
|
||||
|
||||
const replyLeadId = leadIds.length > 1 ? leadIds[1].id : (firstLead ? firstLead.id : null);
|
||||
if (replyLeadId) {
|
||||
db.prepare(
|
||||
`INSERT OR IGNORE INTO outreach_inbound_replies (provider_message_id, lead_id, numero, received_at)
|
||||
VALUES (?, ?, ?, datetime('now'))`
|
||||
).run(`test-inbound-${Date.now()}`, replyLeadId, leadIds[1]?.whatsapp || '5511999990002');
|
||||
}
|
||||
|
||||
let fakeSends = [];
|
||||
|
||||
const whatsapp = {
|
||||
getStatus: () => ({ state: 'READY', connected: true }),
|
||||
getQr: () => null,
|
||||
connect: async () => {},
|
||||
disconnect: async () => {},
|
||||
checkNumber: async (number) => ({
|
||||
registered: true,
|
||||
numero: number,
|
||||
numeroResolvido: number,
|
||||
status: 'confirmado'
|
||||
}),
|
||||
sendText: async () => {
|
||||
fakeSends.push({ at: new Date().toISOString() });
|
||||
throw new Error('fake manager nao envia mensagens reais');
|
||||
}
|
||||
};
|
||||
|
||||
const jobs = { subscribe: () => null, activeJobs: () => [] };
|
||||
|
||||
const server = createAppServer({ db, jobs, whatsapp, log });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(3911, '127.0.0.1', () => {
|
||||
log(`server listening on 3911`);
|
||||
|
||||
const apiRequest = async (method, path, options = {}) => {
|
||||
const url = `http://127.0.0.1:3911${path}`;
|
||||
const headers = { ...options.headers };
|
||||
if (options.body && !headers['content-type']) {
|
||||
headers['content-type'] = 'application/json';
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: options.body ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
let data = null;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch {
|
||||
data = null;
|
||||
}
|
||||
return { status: response.status, headers: response.headers, data, raw: response };
|
||||
};
|
||||
|
||||
const teardown = async () => {
|
||||
await new Promise((r) => server.close(r));
|
||||
db.close();
|
||||
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
||||
log('test server teardown complete');
|
||||
};
|
||||
|
||||
resolve({ server, db, tmpDir, whatsapp, fakeSends, apiRequest, teardown, adminPw });
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user