From 3a23dc393da2dec08a7870ab10c55841034f0f0b Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 24 Sep 2026 22:09:17 -0700 Subject: [PATCH] fix(live): cache the oversized-track 502 like other upstream errors (#722) Keep the new 502, but store it in the 60 s cache as the proxy already does for 404/429: /tracks/all costs OpenSky credits, so an oversized track must not be re-downloaded on every reselect. Update the regression test and credit the fix in CHANGELOG. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 6 ++++++ server/providers/aircraft/tracks.js | 6 +++--- src/tooling/liveProviders.test.mjs | 22 ++++++---------------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419c03e45..8527900d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +- Aircraft track backfill (`/api/opensky-track`, `/api/adsblol/trace`) now + answers 502 when the upstream body exceeds the 5 MB cap, instead of a 200 + whose error body the client read as an empty track. The failure is cached + like other upstream errors, so retries inside the 60 s window do not spend + OpenSky credits (Raushankumar0720, #720, #722). + - Saving a key from Provider Settings works again on Macs where Nix or Homebrew coreutils sit ahead of `/bin` on `PATH`. The credential hardener now spawns Apple's `/bin/chmod -N` by absolute path; GNU `chmod` has no `-N`, diff --git a/server/providers/aircraft/tracks.js b/server/providers/aircraft/tracks.js index d9f2a36d5..2c7b78a78 100644 --- a/server/providers/aircraft/tracks.js +++ b/server/providers/aircraft/tracks.js @@ -55,9 +55,9 @@ export function trackBackfillProxies() { } else { body = text; } - if (!tooLarge) { - cachePut(key, { at: Date.now(), status, body }); - } + // Cache the 502 like any upstream error: /tracks/all costs OpenSky + // credits, so a retry inside the window must not re-download the body. + cachePut(key, { at: Date.now(), status, body }); res.statusCode = status; res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Cache-Control', 'no-store'); diff --git a/src/tooling/liveProviders.test.mjs b/src/tooling/liveProviders.test.mjs index de8681490..38953eff9 100644 --- a/src/tooling/liveProviders.test.mjs +++ b/src/tooling/liveProviders.test.mjs @@ -354,27 +354,17 @@ test('military cooldown bounds untrusted Retry-After and defaults server errors' } }); -test('track backfill proxy returns 502 and skips cache when upstream response exceeds body cap', async (t) => { +test('track backfill proxy returns 502 on an oversized upstream body and caches it as an error', async (t) => { const tracks = install(providers.trackBackfillProxies(), true); let callCount = 0; t.mock.method(globalThis, 'fetch', async () => { callCount++; - if (callCount === 1) { - return { - ok: true, - status: 200, - headers: new Map(), - body: (async function* () { - yield Buffer.alloc(6 * 1024 * 1024, 'x'); - })(), - }; - } return { ok: true, status: 200, headers: new Map(), body: (async function* () { - yield Buffer.from(JSON.stringify({ path: [] })); + yield Buffer.alloc(6 * 1024 * 1024, 'x'); })(), }; }); @@ -385,9 +375,9 @@ test('track backfill proxy returns 502 and skips cache when upstream response ex error: 'Upstream track response too large', }); - // Verify failure was NOT cached: second request invokes upstream again and succeeds + // Cached as a 502 (never a 200): a retry inside the window neither + // reads as an empty track nor spends OpenSky credits on another download. const res2 = await tracks('/api/opensky-track', '?icao24=def456'); - assert.equal(res2.statusCode, 200); - assert.deepEqual(JSON.parse(res2.body), { path: [] }); - assert.equal(callCount, 2); + assert.equal(res2.statusCode, 502); + assert.equal(callCount, 1); });