Files
COMERCIAL-GTI/tests/google-places.test.mjs
T
Gustavo 3be769408c Fix google-places losing already-fetched leads on a mid-pagination failure
search() accumulated results across up to 3 pages in a local array, but a
failure on page 2 or 3 (e.g. Google's next_page_token not active yet
despite the 2.5s wait, or a transient error) propagated out and lost the
whole accumulated array, not just the failing page. The caller's catch
only logs and continues, discarding leads that were already successfully
fetched. Now only rethrows (preserving existing behavior) when nothing has
been accumulated yet; a failure after at least one successful page logs
and returns what was already collected.

Found via a full-project audit.
2026-07-31 22:16:53 -03:00

89 lines
3.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { collect } from '../src/sources/google-places.mjs';
function fakeCtx() {
const logs = [];
return { emit: () => {}, log: (message) => logs.push(message), isCancelled: () => false, logs };
}
// Antes, uma falha na pagina 2/3 (comum: o next_page_token do Google ainda
// nao ativou apesar da espera de 2.5s entre paginas, ou uma falha transitoria
// de rede) fazia search() lancar e descartar TODOS os leads ja obtidos nas
// paginas anteriores bem-sucedidas, nao so a pagina que falhou.
test('falha na pagina 2 nao descarta os leads ja obtidos na pagina 1', async () => {
process.env.GOOGLE_PLACES_API_KEY = 'chave-teste';
const originalFetch = globalThis.fetch;
let textsearchCalls = 0;
globalThis.fetch = async (url) => {
const href = url.toString();
if (href.includes('textsearch')) {
textsearchCalls += 1;
if (textsearchCalls === 1) {
return new Response(JSON.stringify({
status: 'OK',
results: [{ place_id: 'p1', name: 'Pizzaria Um' }],
next_page_token: 'tok-pagina-2'
}));
}
// Pagina 2: falha transitoria (token ainda nao ativo, erro do provedor, etc.)
return new Response(JSON.stringify({ status: 'INVALID_REQUEST' }));
}
if (href.includes('details')) {
return new Response(JSON.stringify({ result: { place_id: 'p1', name: 'Pizzaria Um' } }));
}
throw new Error(`fetch inesperado: ${href}`);
};
try {
const ctx = fakeCtx();
const params = {
uf: 'BA',
cities: ['Salvador'],
segments: [{ name: 'restaurantes', queries: ['pizzaria'], interest: 'comida', pains: [] }],
limits: { maxPerQuery: 10, googlePages: 3 }
};
const batches = [];
for await (const batch of collect(params, ctx)) batches.push(batch);
assert.equal(batches.length, 1, JSON.stringify(batches));
assert.equal(batches[0].leads.length, 1);
assert.equal(batches[0].leads[0].company.name, 'Pizzaria Um');
assert.ok(ctx.logs.some((message) => /pagina 2/.test(message)), ctx.logs.join(' | '));
} finally {
globalThis.fetch = originalFetch;
delete process.env.GOOGLE_PLACES_API_KEY;
}
});
// Uma falha logo na primeira pagina (nada acumulado ainda) continua
// propagando e sendo tratada pelo chamador (loga e pula a query inteira) —
// esse caminho nao muda.
test('falha na primeira pagina ainda propaga e pula a query', async () => {
process.env.GOOGLE_PLACES_API_KEY = 'chave-teste';
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => new Response(JSON.stringify({ status: 'INVALID_REQUEST' }));
try {
const ctx = fakeCtx();
const params = {
uf: 'BA',
cities: ['Salvador'],
segments: [{ name: 'restaurantes', queries: ['pizzaria'], interest: 'comida', pains: [] }],
limits: { maxPerQuery: 10, googlePages: 3 }
};
const batches = [];
for await (const batch of collect(params, ctx)) batches.push(batch);
assert.equal(batches.length, 0);
assert.ok(ctx.logs.some((message) => /falhou em/.test(message)), ctx.logs.join(' | '));
} finally {
globalThis.fetch = originalFetch;
delete process.env.GOOGLE_PLACES_API_KEY;
}
});