Files
COMERCIAL-GTI/tests/capture-routes.test.mjs
T
Gustavo d330bb5ff0 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.
2026-07-31 10:54:07 -03:00

126 lines
5.0 KiB
JavaScript

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