mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
fix(portwatch): proxy fallback on ArcGIS 429 + verbose SKIPPED log (#2801)
* fix(portwatch): proxy fallback on ArcGIS 429 + verbose SKIPPED log * fix(portwatch): use resolveProxyForConnect for httpsProxyFetchRaw CONNECT tunnel * test(portwatch): assert 429 proxy fallback and SKIPPED log coverage
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { loadEnvFile, runSeed, CHROME_UA } from './_seed-utils.mjs';
|
||||
import { loadEnvFile, runSeed, CHROME_UA, resolveProxyForConnect, httpsProxyFetchRaw } from './_seed-utils.mjs';
|
||||
|
||||
loadEnvFile(import.meta.url);
|
||||
|
||||
@@ -29,8 +29,16 @@ export async function fetchAll() {
|
||||
headers: { 'User-Agent': CHROME_UA, Accept: 'application/json' },
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
||||
});
|
||||
let body;
|
||||
if (resp.status === 429) {
|
||||
const proxyAuth = resolveProxyForConnect();
|
||||
if (!proxyAuth) throw new Error('ArcGIS HTTP 429 (rate limited) and no PROXY_URL configured');
|
||||
const { buffer } = await httpsProxyFetchRaw(`${ARCGIS_BASE}?${params}`, proxyAuth, { accept: 'application/json', timeoutMs: FETCH_TIMEOUT });
|
||||
body = JSON.parse(buffer.toString('utf8'));
|
||||
} else {
|
||||
if (!resp.ok) throw new Error(`ArcGIS HTTP ${resp.status}`);
|
||||
const body = await resp.json();
|
||||
body = await resp.json();
|
||||
}
|
||||
if (body.error) throw new Error(`ArcGIS chokepoints-ref error: ${body.error.message}`);
|
||||
|
||||
const features = body.features ?? [];
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
extendExistingTtl,
|
||||
logSeedResult,
|
||||
readSeedSnapshot,
|
||||
resolveProxyForConnect,
|
||||
httpsProxyFetchRaw,
|
||||
} from './_seed-utils.mjs';
|
||||
import { createCountryResolvers } from './_country-resolver.mjs';
|
||||
|
||||
@@ -44,6 +46,14 @@ async function fetchWithTimeout(url) {
|
||||
headers: { 'User-Agent': CHROME_UA, Accept: 'application/json' },
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
||||
});
|
||||
if (resp.status === 429) {
|
||||
const proxyAuth = resolveProxyForConnect();
|
||||
if (!proxyAuth) throw new Error(`ArcGIS HTTP 429 (rate limited) for ${url.slice(0, 80)}`);
|
||||
const { buffer } = await httpsProxyFetchRaw(url, proxyAuth, { accept: 'application/json', timeoutMs: FETCH_TIMEOUT });
|
||||
const proxied = JSON.parse(buffer.toString('utf8'));
|
||||
if (proxied.error) throw new Error(`ArcGIS error (via proxy): ${proxied.error.message}`);
|
||||
return proxied;
|
||||
}
|
||||
if (!resp.ok) throw new Error(`ArcGIS HTTP ${resp.status} for ${url.slice(0, 80)}`);
|
||||
const body = await resp.json();
|
||||
if (body.error) throw new Error(`ArcGIS error: ${body.error.message}`);
|
||||
@@ -239,7 +249,7 @@ async function main() {
|
||||
const lock = await acquireLockSafely(LOCK_DOMAIN, runId, LOCK_TTL_MS, { label: LOCK_DOMAIN });
|
||||
if (lock.skipped) return;
|
||||
if (!lock.locked) {
|
||||
console.log(' SKIPPED: another seed run in progress');
|
||||
console.log(` SKIPPED: another seed run in progress (lock: seed-lock:${LOCK_DOMAIN}, held up to ${LOCK_TTL_MS / 60000}min — will retry at next cron trigger)`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,29 @@ describe('seed-portwatch-chokepoints-ref.mjs exports', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ArcGIS 429 proxy fallback', () => {
|
||||
it('imports resolveProxyForConnect and httpsProxyFetchRaw', () => {
|
||||
assert.match(src, /resolveProxyForConnect/);
|
||||
assert.match(src, /httpsProxyFetchRaw/);
|
||||
});
|
||||
|
||||
it('fetchAll checks resp.status === 429', () => {
|
||||
assert.match(src, /resp\.status\s*===\s*429/);
|
||||
});
|
||||
|
||||
it('calls resolveProxyForConnect() on 429', () => {
|
||||
assert.match(src, /resolveProxyForConnect\(\)/);
|
||||
});
|
||||
|
||||
it('calls httpsProxyFetchRaw with proxy auth on 429', () => {
|
||||
assert.match(src, /httpsProxyFetchRaw\(.*proxyAuth/s);
|
||||
});
|
||||
|
||||
it('throws if 429 and no proxy configured', () => {
|
||||
assert.match(src, /429.*rate limited/);
|
||||
});
|
||||
});
|
||||
|
||||
// ── unit tests for chokepoint reference data building ─────────────────────────
|
||||
|
||||
function buildEntry(a) {
|
||||
|
||||
@@ -67,6 +67,43 @@ describe('seed-portwatch-port-activity.mjs exports', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ArcGIS 429 proxy fallback', () => {
|
||||
it('imports resolveProxyForConnect and httpsProxyFetchRaw', () => {
|
||||
assert.match(src, /resolveProxyForConnect/);
|
||||
assert.match(src, /httpsProxyFetchRaw/);
|
||||
});
|
||||
|
||||
it('fetchWithTimeout checks resp.status === 429', () => {
|
||||
assert.match(src, /resp\.status\s*===\s*429/);
|
||||
});
|
||||
|
||||
it('calls resolveProxyForConnect() on 429', () => {
|
||||
assert.match(src, /resolveProxyForConnect\(\)/);
|
||||
});
|
||||
|
||||
it('calls httpsProxyFetchRaw with proxy auth on 429', () => {
|
||||
assert.match(src, /httpsProxyFetchRaw\(url,\s*proxyAuth/);
|
||||
});
|
||||
|
||||
it('throws if 429 and no proxy configured', () => {
|
||||
assert.match(src, /429.*rate limited/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SKIPPED log message', () => {
|
||||
it('includes lock domain in SKIPPED message', () => {
|
||||
assert.match(src, /SKIPPED.*seed-lock.*LOCK_DOMAIN/s);
|
||||
});
|
||||
|
||||
it('includes TTL duration in SKIPPED message', () => {
|
||||
assert.match(src, /LOCK_TTL_MS\s*\/\s*60000/);
|
||||
});
|
||||
|
||||
it('mentions next cron trigger in SKIPPED message', () => {
|
||||
assert.match(src, /next cron trigger/);
|
||||
});
|
||||
});
|
||||
|
||||
// ── unit tests ────────────────────────────────────────────────────────────────
|
||||
|
||||
function computeAnomalySignal(rows, cutoff30, cutoff7) {
|
||||
|
||||
Reference in New Issue
Block a user