chore: import maquina-comercial-pessoal history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: maquina-comercial-pessoal git-subtree-mainline:f5af038e19git-subtree-split:a9ef4d776d
This commit is contained in:
@@ -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();
|
||||
});
|
||||
Reference in New Issue
Block a user