From 614bbf9290bcfc556d613568987d3d4cfbf58db9 Mon Sep 17 00:00:00 2001 From: JB Date: Sat, 19 Sep 2026 10:02:01 -0400 Subject: [PATCH] test: cover enabled anonymous rate limits --- scripts/release-gate-smoke.ts | 43 ++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/scripts/release-gate-smoke.ts b/scripts/release-gate-smoke.ts index dd65e04..d31b901 100644 --- a/scripts/release-gate-smoke.ts +++ b/scripts/release-gate-smoke.ts @@ -121,7 +121,48 @@ try { const liveAssets = await fetch(`${liveBase}/assets`, { headers: cookie }); await assertStatus(liveAssets, 200, 'authenticated asset gate'); if (trapCalls !== 0) throw new Error(`disabled route performed ${trapCalls} Convex session calls`); - console.log(`Rollout-gate HTTP smoke passed: anonymous and authenticated disabled paths avoided provider/session calls`); + server.kill('SIGTERM'); + await server.exited; + server = undefined; + + // Enabled live-mode regression: no-cookie requests resolve to anonymous + // users, so the post-session limiter must not reuse the same IP key. Sync + // reaches its 401 response and chat uses invalid JSON schema to avoid every + // provider while still exercising both limiter passes. + const enabledPort = await freePort(); + const enabledBase = `http://127.0.0.1:${enabledPort}`; + const enabledRelease = `gate-enabled-${crypto.randomUUID()}`; + server = Bun.spawn(['bun', 'build/index.js'], { + env: { + ...safeEnv, APP_MODE: 'live', APP_ORIGIN: enabledBase, ORIGIN: enabledBase, HOST: '127.0.0.1', PORT: String(enabledPort), + REFERENCE_DIR: referenceDir, APP_RELEASE_ID: enabledRelease, APP_CAPABILITY_ALLOWLIST: 'asset_sync,assistant_dispatch', + CONVEX_URL: `http://127.0.0.1:${trap.port}`, PUBLIC_CONVEX_URL: `http://127.0.0.1:${trap.port}`, + CONVEX_BRIDGE_SECRET: 'synthetic-bridge-secret', TYPESAFE_API_KEY: 'sentinel-not-used', + }, stdout: 'pipe', stderr: 'pipe', stdin: 'ignore', + }); + await waitForServer(enabledBase, enabledRelease); + const enabledSyncRequest = () => fetch(`${enabledBase}/api/sync`, { method: 'POST', headers: { origin: enabledBase } }); + for (let attempt = 1; attempt <= 6; attempt += 1) { + const response = await enabledSyncRequest(); + await assertStatus(response, 401, `enabled anonymous sync denial ${attempt}`); + await response.text(); + } + const enabledSyncLimited = await enabledSyncRequest(); + await assertStatus(enabledSyncLimited, 429, 'enabled anonymous sync rate limit'); + await enabledSyncLimited.text(); + const enabledChatRequest = () => fetch(`${enabledBase}/api/chat`, { + method: 'POST', headers: { origin: enabledBase, 'content-type': 'application/json' }, body: '{}', + }); + for (let attempt = 1; attempt <= 30; attempt += 1) { + const response = await enabledChatRequest(); + await assertStatus(response, 400, `enabled anonymous chat denial ${attempt}`); + await response.text(); + } + const enabledChatLimited = await enabledChatRequest(); + await assertStatus(enabledChatLimited, 429, 'enabled anonymous chat rate limit'); + await enabledChatLimited.text(); + if (trapCalls !== 0) throw new Error(`enabled anonymous routes performed ${trapCalls} Convex/provider calls`); + console.log(`Rollout-gate HTTP smoke passed: anonymous thresholds and authenticated disabled paths avoided provider/session calls`); } finally { if (server) { server.kill('SIGTERM'); await server.exited; } trap?.stop();