import { beforeEach, describe, expect, mock, test } from "bun:test"; import { fakeStorage } from "./do-test-storage"; const env: Record = {}; mock.module("cloudflare:workers", () => ({ env, DurableObject: class { ctx: unknown; env: unknown; constructor(ctx: unknown, workerEnv: unknown) { this.ctx = ctx; this.env = workerEnv; } }, })); mock.module("@tanstack/react-start/server", () => ({ getRequestHeader: () => null })); mock.module("./db", () => ({ first: async () => ({ hits: 1 }), run: async () => undefined })); const { RateLimiter } = await import("./rate-limiter-do"); const { rateLimitDigest, rateLimitShard, RATE_LIMIT_SHARDS } = await import("./rate-limit-shard"); const { enforceRateLimit, withinRateLimit, limitWrite, limitBrowse, resetRateLimitCache, RATE_LIMITS } = await import("./limits"); type Limiter = InstanceType; let shards: Map; object: Limiter }>; let objectDown = false; let platformSays = true; let rpcCalls = 0; function shard(name: string) { let entry = shards.get(name); if (!entry) { const storage = fakeStorage(); entry = { storage, object: new RateLimiter({ storage } as never, {} as never) }; shards.set(name, entry); } return entry; } function installEnv(): void { env.PUBLIC_URL = "https://forge.example.com"; delete env.RATE_LIMITS_DISABLED; for (const name of Object.keys(RATE_LIMITS)) env[name] = { limit: async () => ({ success: platformSays }) }; env.RATE_LIMITER = { idFromName: (name: string) => name, get: (name: string) => ({ hit: async (key: string, limit: number, periodMs: number) => { rpcCalls++; if (objectDown) throw new Error("object unreachable"); return shard(name).object.hit(key, limit, periodMs); }, }), }; } beforeEach(() => { shards = new Map(); objectDown = false; platformSays = true; rpcCalls = 0; resetRateLimitCache(); installEnv(); }); async function status(promise: Promise): Promise { try { await promise; return 200; } catch (error) { return (error as { status: number }).status; } } describe("RateLimiter object", () => { test("counts per key within a window and resets in the next", async () => { const limiter = shard("x").object; const base = 600_000; expect((await limiter.hit("a", 2, 60_000, base)).success).toBe(true); expect((await limiter.hit("a", 2, 60_000, base + 1)).success).toBe(true); expect((await limiter.hit("a", 2, 60_000, base + 2)).success).toBe(false); expect((await limiter.hit("b", 2, 60_000, base + 2)).success).toBe(true); expect((await limiter.hit("a", 2, 60_000, base + 60_000)).success).toBe(true); }); test("survives a restart on the same storage", async () => { const { storage, object } = shard("x"); const base = 600_000; await object.hit("a", 1, 60_000, base); const restarted = new RateLimiter({ storage } as never, {} as never); expect((await restarted.hit("a", 1, 60_000, base + 5)).success).toBe(false); }); test("alarm prunes expired windows and rearms for the rest", async () => { const { storage, object } = shard("x"); const now = Date.now(); await object.hit("old", 5, 1, now - 10_000); await object.hit("live", 5, 3_600_000, now); await object.alarm(); const keys = storage.sql.exec<{ key: string }>("SELECT key FROM windows").toArray(); expect(keys.map((row) => row.key)).toEqual(["live"]); expect(storage.alarmAt).toBeGreaterThan(now); }); }); describe("sharding", () => { test("is deterministic and spreads keys", async () => { const digest = await rateLimitDigest("GIT_LIMITER", "ip:1.2.3.4"); expect(rateLimitShard(digest)).toBe(rateLimitShard(await rateLimitDigest("GIT_LIMITER", "ip:1.2.3.4"))); expect(digest).not.toBe(await rateLimitDigest("AUTH_LIMITER", "ip:1.2.3.4")); const names = new Set(); const sampleKeys = 500; for (let index = 0; index < sampleKeys; index++) names.add(rateLimitShard(await rateLimitDigest("GIT_LIMITER", `k${index}`))); expect(names.size).toBeGreaterThan(RATE_LIMIT_SHARDS / 2); for (const name of names) expect(Number(name.slice(3))).toBeLessThan(RATE_LIMIT_SHARDS); }); }); describe("enforceRateLimit", () => { test("enforces REPORT 5/min per key even when the platform binding never refuses", async () => { for (let index = 0; index < RATE_LIMITS.REPORT_LIMITER.limit; index++) expect(await status(enforceRateLimit("REPORT_LIMITER", "ip:a"))).toBe(200); expect(await status(enforceRateLimit("REPORT_LIMITER", "ip:a"))).toBe(429); expect(await status(enforceRateLimit("REPORT_LIMITER", "ip:b"))).toBe(200); }); test("honors a platform refusal", async () => { platformSays = false; expect(await status(enforceRateLimit("WRITE_LIMITER", "user:1"))).toBe(429); }); test("caches refusals locally so a flood stops costing RPCs", async () => { for (let index = 0; index <= RATE_LIMITS.REPORT_LIMITER.limit; index++) await status(enforceRateLimit("REPORT_LIMITER", "ip:a")); const calls = rpcCalls; expect(await status(enforceRateLimit("REPORT_LIMITER", "ip:a"))).toBe(429); expect(rpcCalls).toBe(calls); }); test("AUTH and REPORT fail closed, WRITE/HEAVY/GIT fail open when the object is down", async () => { objectDown = true; const errors = mock(() => undefined); const original = console.error; console.error = errors; try { expect(await status(enforceRateLimit("AUTH_LIMITER", "ip:a"))).toBe(503); expect(await status(enforceRateLimit("REPORT_LIMITER", "ip:a"))).toBe(503); expect(await withinRateLimit("AUTH_LIMITER", "ip:a")).toBe(false); expect(await status(enforceRateLimit("WRITE_LIMITER", "user:1"))).toBe(200); expect(await status(enforceRateLimit("HEAVY_LIMITER", "user:1"))).toBe(200); expect(await status(enforceRateLimit("GIT_LIMITER", "ip:a"))).toBe(200); expect(errors).toHaveBeenCalled(); } finally { console.error = original; } }); test("RATE_LIMITS_DISABLED still works on loopback only", async () => { env.RATE_LIMITS_DISABLED = "true"; objectDown = true; const original = console.error; console.error = () => undefined; try { expect(await status(enforceRateLimit("AUTH_LIMITER", "ip:a"))).toBe(503); } finally { console.error = original; } env.PUBLIC_URL = "http://localhost:3000"; expect(await status(enforceRateLimit("AUTH_LIMITER", "ip:a"))).toBe(200); expect(rpcCalls).toBe(1); }); test("existing callers keep their limiter and keys", async () => { for (let index = 0; index < RATE_LIMITS.WRITE_LIMITER.limit; index++) await limitWrite({ id: "u1", emailVerified: true } as never); expect(await status(limitWrite({ id: "u1", emailVerified: true } as never))).toBe(429); for (let index = 0; index < RATE_LIMITS.GIT_LIMITER.limit; index++) await limitBrowse(null); expect(await status(limitBrowse(null))).toBe(429); expect(await withinRateLimit("WRITE_LIMITER", "user:u2")).toBe(true); }); test("local overhead per check stays well under a millisecond", async () => { const started = performance.now(); const checks = 1000; for (let index = 0; index < checks; index++) await withinRateLimit("GIT_LIMITER", `ip:${index}`); const perCall = (performance.now() - started) / checks; console.log(`rate limit check overhead (excluding network): ${perCall.toFixed(3)} ms`); expect(perCall).toBeLessThan(5); }); });