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:
Gustavo
2026-07-31 10:54:07 -03:00
parent 6cd336a41e
commit d330bb5ff0
132 changed files with 29969 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { openDb } from '../src/db.mjs';
import { createRouter } from '../src/server/router.mjs';
import { registerCaptureRoutes } from '../src/server/routes/capture.mjs';
import { registerExtractionRoutes } from '../src/server/routes/extractions.mjs';
function setup() {
const db = openDb(':memory:');
db.prepare("INSERT INTO municipios (codigo_ibge, nome, nome_clean, uf) VALUES ('1','Salvador','salvador','BA')").run();
db.prepare("INSERT INTO segments (name,label,active) VALUES ('restaurantes','Restaurantes',1)").run();
db.prepare("INSERT INTO cnaes (codigo,descricao) VALUES ('5611201','Restaurantes')").run();
const router = createRouter();
let started;
registerCaptureRoutes(router, { db, jobs: { startJob: (options) => { started = options; return 'job-1'; } } });
return { db, handler: router.match('POST', '/api/capture/runs').handler, get started() { return started; } };
}
async function invoke(handler, body) {
let result;
await handler({}, {}, { body, json: (status, data) => { result = { status, data }; } });
return result;
}
const valid = {
sources: ['mock'], uf: 'BA', cities: ['Salvador'], segments: ['restaurantes'],
limits: { maxPerQuery: 10, queryLimit: 0, googlePages: 1 }
};
test('captura aceita apenas selecoes reais e persiste parametros normalizados', async () => {
const fixture = setup();
const { db, handler } = fixture;
const result = await invoke(handler, valid);
assert.equal(result.status, 202);
assert.deepEqual(JSON.parse(db.prepare('SELECT params_json FROM capture_runs').get().params_json), valid);
const row = db.prepare('SELECT job_id, owner_token, heartbeat_at FROM capture_runs').get();
assert.equal(row.job_id, 'job-1');
assert.ok(row.owner_token);
assert.ok(row.heartbeat_at);
assert.equal(fixture.started.ownerToken, row.owner_token);
db.close();
});
test('captura rejeita arrays vazios, duplicados, excessivos e strings longas', async () => {
for (const patch of [
{ sources: [] },
{ cities: [] },
{ segments: [] },
{ sources: ['mock', 'mock'] },
{ sources: Array(11).fill('mock') },
{ cities: Array(1001).fill('Salvador') },
{ segments: Array(251).fill('restaurantes') },
{ cities: ['x'.repeat(101)] }
]) {
const { db, handler } = setup();
assert.equal((await invoke(handler, { ...valid, ...patch })).status, 400);
db.close();
}
});
test('captura rejeita cidade, segmento, CNAE e limites inexistentes ou fora da faixa', async () => {
for (const patch of [
{ cities: ['Feira de Santana'] },
{ cities: ['all', 'Salvador'] },
{ segments: ['inativo'] },
{ segments: ['cnae:123'] },
{ segments: ['cnae:1234567'] },
{ limits: { maxPerQuery: 0, queryLimit: 0, googlePages: 1 } },
{ limits: { maxPerQuery: 101, queryLimit: 0, googlePages: 1 } },
{ limits: { maxPerQuery: 10, queryLimit: 10001, googlePages: 1 } },
{ limits: { maxPerQuery: 10, queryLimit: 0, googlePages: 4 } }
]) {
const { db, handler } = setup();
assert.equal((await invoke(handler, { ...valid, ...patch })).status, 400);
db.close();
}
});
test('captura converte corrida de constraint em 409 sem row parcial', async () => {
const db = openDb(':memory:');
db.prepare("INSERT INTO municipios (codigo_ibge, nome, nome_clean, uf) VALUES ('1','Salvador','salvador','BA')").run();
db.prepare("INSERT INTO segments (name,label,active) VALUES ('restaurantes','Restaurantes',1)").run();
const raceDb = {
prepare(sql) {
if (/INSERT INTO capture_runs/.test(sql)) {
const error = new Error('UNIQUE constraint failed: active capture');
error.errcode = 2067;
throw error;
}
return db.prepare(sql);
}
};
const router = createRouter();
let started = false;
registerCaptureRoutes(router, { db: raceDb, jobs: { startJob: () => { started = true; } } });
const result = await invoke(router.match('POST', '/api/capture/runs').handler, valid);
assert.equal(result.status, 409);
assert.match(result.data.erro, /simultaneamente/i);
assert.equal(db.prepare('SELECT COUNT(*) total FROM capture_runs').get().total, 0);
assert.equal(started, false);
db.close();
});
test('extracao converte corrida de constraint em 409 sem row parcial', async () => {
const db = openDb(':memory:');
const raceDb = {
prepare(sql) {
if (/INSERT INTO extractions/.test(sql)) {
const error = new Error('UNIQUE constraint failed: active extraction');
error.errcode = 2067;
throw error;
}
return db.prepare(sql);
}
};
const router = createRouter();
let started = false;
registerExtractionRoutes(router, { db: raceDb, jobs: { startJob: () => { started = true; } } });
const result = await invoke(router.match('POST', '/api/extractions').handler, { uf: 'BA' });
assert.equal(result.status, 409);
assert.match(result.data.erro, /simultaneamente/i);
assert.equal(db.prepare('SELECT COUNT(*) total FROM extractions').get().total, 0);
assert.equal(started, false);
db.close();
});