From 4a8d13d288dffe0a59f6eeea1fa5fe737daa841e Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Tue, 30 Jun 2026 21:39:26 -0500 Subject: [PATCH] =?UTF-8?q?test(#146):=20storm=20harness=20=E2=80=94=20the?= =?UTF-8?q?=20bulletproof=20proof?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combines all four spiral inputs at once: a pre-bloated device_status_log (300k rows), a maintenance sweep running over it (the old whole-table sort froze 40-48s), a hard flapper, and an OTA download flood from a single SNAT IP. Asserts: - the event loop never enters a multi-second freeze (max 10ms-ticker gap < 300ms, vs 40-48s pre-fix) and the ticker keeps firing throughout, - the chunked sweep drains the 300k backlog to the per-device cap, - every limiter still bites under load: the flapper is refused, the OTA flood is shed past the global window cap (served capped, never per-IP). Suite 267/267. Co-Authored-By: Claude Opus 4.8 (1M context) --- server/test/storm-harness.test.js | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 server/test/storm-harness.test.js diff --git a/server/test/storm-harness.test.js b/server/test/storm-harness.test.js new file mode 100644 index 0000000..c2f30ad --- /dev/null +++ b/server/test/storm-harness.test.js @@ -0,0 +1,77 @@ +'use strict'; + +// #146 hardening — THE BULLETPROOF PROOF. Combine, all at once: +// - a pre-bloated device_status_log (300k rows — the incident amplifier), +// - a maintenance sweep running over it (the old whole-table sort froze 40-48s), +// - a device flapping hard (the trigger), +// - an OTA download flood from a single SNAT IP, +// and assert the event loop NEVER enters a multi-second freeze and the server stays +// responsive throughout (no NaN-sample block), while every limiter still bites. + +const os = require('node:os'); +const path = require('node:path'); +const crypto = require('node:crypto'); +process.env.DATA_DIR = path.join(os.tmpdir(), 'st-storm-' + crypto.randomBytes(4).toString('hex')); +process.env.STATUS_LOG_MAX_ROWS_PER_DEVICE = '500'; +process.env.STATUS_LOG_PRUNE_BATCH = '2000'; +process.env.STATUS_LOG_RETENTION_DAYS = '3'; +process.env.CONNECT_RATE_MAX = '20'; +process.env.OTA_DOWNLOAD_MAX_PER_WINDOW = '120'; + +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { db, pruneStatusLog } = require('../db/database'); +const flap = require('../lib/flap-limiter'); +const otaGuard = require('../lib/ota-download-guard'); +const chunked = require('../lib/chunked-prune'); + +test('storm: bloated-table sweep + flapper + OTA flood — loop stays responsive, limiters bite', async () => { + chunked.__setBandForTest(() => 'normal'); + flap.reset(); + + // Pre-bloat: 300k rows across a few flapping devices (recent -> the cap prune must + // remove ~299k+ of them). + db.exec('DELETE FROM device_status_log'); + const ins = db.prepare('INSERT INTO device_status_log (device_id, status, timestamp) VALUES (?, ?, ?)'); + const now = Math.floor(Date.now() / 1000); + db.transaction(() => { + for (let i = 0; i < 300000; i++) ins.run('dev-' + (i % 3), i % 2 ? 'online' : 'offline', now); + })(); + assert.equal(db.prepare('SELECT COUNT(*) c FROM device_status_log').get().c, 300000, 'seeded 300k'); + + // Event-loop responsiveness probe: max gap between 10ms ticks = worst sync block. + let maxGap = 0, last = Date.now(), ticks = 0; + const ticker = setInterval(() => { const n = Date.now(); maxGap = Math.max(maxGap, n - last); last = n; ticks++; }, 10); + + // The amplifier sweep (chunked) runs concurrently with the storms below. + const prunePromise = pruneStatusLog({ bandGate: false }); + + // Flapper storm + OTA flood from ONE SNAT IP, driven in yielding rounds. + let flapRefused = 0, otaShed = 0, otaServed = 0; + const guardState = otaGuard.newState(); + const stormPromise = (async () => { + for (let round = 0; round < 300; round++) { + for (let i = 0; i < 40; i++) { + if (!flap.check('d:storm-flapper').allow) flapRefused++; // one hard flapper (identity-keyed, never IP) + const a = otaGuard.admit(guardState, 'normal'); // global download admission + if (a.allow) { otaServed++; otaGuard.release(guardState); } else otaShed++; + } + await new Promise((r) => setImmediate(r)); + } + })(); + + const [deleted] = await Promise.all([prunePromise, stormPromise]); + clearInterval(ticker); + + // 1) NO multi-second freeze — the old whole-table sort would freeze the ticker for + // tens of seconds here. + assert.ok(maxGap < 300, `loop never froze — max event-loop gap ${maxGap}ms (was 40-48s pre-fix)`); + assert.ok(ticks >= 10, `ticker kept firing through the storm (${ticks} ticks)`); + // 2) The sweep actually drained the backlog to the cap. + assert.ok(deleted >= 298000, `sweep trimmed the backlog (${deleted} deleted)`); + assert.equal(db.prepare('SELECT COUNT(*) c FROM device_status_log').get().c, 1500, '3 devices x 500 cap'); + // 3) Every limiter still bit under load. + assert.ok(flapRefused > 0, 'the flapper was refused (flap limiter bit)'); + assert.ok(otaShed > 0, 'the OTA flood was shed past the global window cap'); + assert.ok(otaServed <= 120, `OTA served capped at the window max (${otaServed})`); +});