screentinker/server/test/ota-hardening.test.js
ScreenTinker 067aebfd75 fix(#146) P1.2: band-aware download caps — serve freely when healthy
A fixed OTA_DOWNLOAD_MAX_CONCURRENT=10 throttled a legitimate coordinated rollout even
on a perfectly healthy server, and a shed 503 costs a client a full ~30-min re-check
cycle. Made the download guard's concurrency + rate caps band-aware:
  - normal   -> serve FREELY (no cap): a whole-fleet rollout isn't staggered when healthy
  - elevated -> the configured caps engage (early backpressure)
  - critical -> shed 503 (the real protection, unchanged)
Kill switch OTA_DOWNLOAD_GUARD_ENABLED=false disables it entirely.

Tests updated: normal serves 50/50 with 0 shed; caps + shed now asserted under elevated;
storm harness OTA flood runs under elevated (the loaded state). Suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:00:28 -05:00

77 lines
3.4 KiB
JavaScript

'use strict';
// #146 hardening (Item C) — OTA under SNAT. Cached APK resolution (no per-request fs)
// + GLOBAL download admission (concurrency + rate + critical-band shed, never per-IP).
const os = require('node:os');
const path = require('node:path');
const fs = require('node:fs');
const crypto = require('node:crypto');
process.env.DATA_DIR = path.join(os.tmpdir(), 'st-otahard-' + crypto.randomBytes(4).toString('hex'));
process.env.OTA_DOWNLOAD_MAX_CONCURRENT = '3';
process.env.OTA_DOWNLOAD_MAX_PER_WINDOW = '5';
process.env.OTA_DOWNLOAD_WINDOW_MS = '100000';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const guard = require('../lib/ota-download-guard');
const apkCache = require('../lib/apk-cache');
test('apk-cache: get() never touches the filesystem (resolution cached at boot/refresh)', () => {
// seed a fake APK under DATA_DIR and refresh once
const apk = path.join(process.env.DATA_DIR, 'ScreenTinker.apk');
fs.mkdirSync(process.env.DATA_DIR, { recursive: true });
fs.writeFileSync(apk, 'FAKEAPKBYTES');
const c = apkCache.refresh();
assert.equal(c.exists, true);
assert.equal(c.size, 12);
// count fs.statSync calls across many get()s -> must be ZERO (a poll/download flood
// can't become a statSync flood).
const realStat = fs.statSync; let calls = 0;
fs.statSync = (...a) => { calls++; return realStat(...a); };
try { for (let i = 0; i < 1000; i++) apkCache.get(); } finally { fs.statSync = realStat; }
assert.equal(calls, 0, 'get() does no fs; 1000 reads = 0 statSync');
});
test('band-aware: NORMAL band serves freely (no cap) — healthy rollout not staggered', () => {
const s = guard.newState();
for (let i = 0; i < 50; i++) assert.equal(guard.admit(s, 'normal').allow, true, 'normal band never caps');
assert.equal(s.shed, 0, 'nothing shed while healthy');
});
test('download guard: ELEVATED band applies the global concurrency cap -> 503 (not per-IP)', () => {
const s = guard.newState();
assert.equal(guard.admit(s, 'elevated').allow, true);
assert.equal(guard.admit(s, 'elevated').allow, true);
assert.equal(guard.admit(s, 'elevated').allow, true); // 3 in-flight = cap
const over = guard.admit(s, 'elevated');
assert.equal(over.allow, false);
assert.equal(over.status, 503);
assert.ok(over.retryAfter > 0);
guard.release(s); // free one slot
assert.equal(guard.admit(s, 'elevated').allow, true, 'a freed slot admits again');
});
test('download guard: ELEVATED band applies the global per-window rate cap -> 503', () => {
const s = guard.newState();
for (let i = 0; i < 5; i++) { assert.equal(guard.admit(s, 'elevated').allow, true); guard.release(s); } // 5 served this window
const over = guard.admit(s, 'elevated');
assert.equal(over.allow, false, '6th in the window is shed');
assert.equal(over.status, 503);
});
test('download guard: critical band sheds regardless of caps', () => {
const s = guard.newState();
const v = guard.admit(s, 'critical');
assert.equal(v.allow, false);
assert.equal(v.retryAfter, 30, 'critical band asks for a longer backoff');
});
test('download guard admission takes NO ip argument (global by construction)', () => {
// admit(state, band, now) — there is no IP parameter, so it cannot key on IP.
assert.equal(guard.admit.length <= 3, true);
const s = guard.newState();
assert.equal(guard.admit(s, 'normal').allow, true); // works with zero IP context
});