← back to La Socrata Ingester
test/arcgis-retry.test.mjs
66 lines
// Regression test for TK-10851: ArcGIS tunnels errors as HTTP 200 with an
// {error:{code}} body, so the HTTP-level retry never sees them. fetchArcgis must
// RETRY transient codes (499/CONT_0001, 429, 5xx) and FAIL FAST on permanent ones (400).
// Run: node test/arcgis-retry.test.mjs (zero deps; ~3s due to real backoff)
import { arcgisPages } from '../src/adapters/arcgis.js';
const src = { endpoint: 'http://fake/FeatureServer/0', paginate: 'oid', oidField: 'OBJECTID', geometry: false };
let fail = 0;
// 1) transient 499 twice, then success — must retry through, not throw
let calls = 0;
globalThis.fetch = async () => {
calls++;
const body = calls <= 2
? { error: { code: 499, messageCode: 'CONT_0001', message: 'Item does not exist or is inaccessible.' } }
: { features: [{ attributes: { OBJECTID: 1, APN: '1234' } }] };
return { ok: true, status: 200, json: async () => body, text: async () => JSON.stringify(body) };
};
try {
let rows = 0;
for await (const page of arcgisPages(src, { maxRows: 1 })) rows += page.rows.length;
const ok = rows === 1 && calls === 3;
console.log(`TRANSIENT: ${ok ? 'PASS (retried through 2x 499, then succeeded)' : `FAIL (rows=${rows} calls=${calls})`}`);
if (!ok) fail++;
} catch (e) {
console.log(`TRANSIENT: FAIL (threw instead of retrying: ${e.message}, calls=${calls})`);
fail++;
}
// 2) permanent 400 — must throw immediately, no backoff
let calls2 = 0;
globalThis.fetch = async () => {
calls2++;
const b = { error: { code: 400, message: 'Invalid query parameters' } };
return { ok: true, status: 200, json: async () => b, text: async () => JSON.stringify(b) };
};
try {
for await (const _ of arcgisPages(src, { maxRows: 1 })) { /* consume */ }
console.log('PERMANENT: FAIL (did not throw on 400)');
fail++;
} catch {
const ok = calls2 === 1;
console.log(`PERMANENT: ${ok ? 'PASS (400 threw immediately, no retry)' : `FAIL (retried a permanent error, calls=${calls2})`}`);
if (!ok) fail++;
}
// 3) bare 499 "Token Required" (no CONT_0001) — a permanent auth failure, must fail fast
let calls3 = 0;
globalThis.fetch = async () => {
calls3++;
const b = { error: { code: 499, messageCode: 'GWM_0003', message: 'Token Required' } };
return { ok: true, status: 200, json: async () => b, text: async () => JSON.stringify(b) };
};
try {
for await (const _ of arcgisPages(src, { maxRows: 1 })) { /* consume */ }
console.log('AUTH-499: FAIL (retried/masked a Token-Required auth failure)');
fail++;
} catch {
const ok = calls3 === 1;
console.log(`AUTH-499: ${ok ? 'PASS (bare 499 threw immediately, not masked)' : `FAIL (retried a 499 auth error, calls=${calls3})`}`);
if (!ok) fail++;
}
process.exitCode = fail ? 1 : 0;
console.log(fail ? `\n${fail} check(s) FAILED` : '\nAll checks PASS');