import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner"; import { FixedWindow } from "../src/core/fixed-window/fixed-window.js"; import { createRateLimiter } from "../src/core/rate-limiter/rate-limiter.js"; import { SlidingWindow } from "../src/core/sliding-window/sliding-window.js"; import { TokenBucket } from "../src/core/token-bucket/token-bucket.js"; import { MemoryStorage } from "../src/storage/memory-storage/memory-storage.js"; describe("Integration: multi-algorithm coordination", () => { let t: number; let clock: () => number; beforeEach(() => { t = 1_000_000; clock = () => t; }); it("token bucket + storage round-trip", { timeout: 5000 }, () => { const bucket = new TokenBucket({ capacity: 10, refillRate: 0.01, clock, }); // Drain bucket for (let i = 0; i < 10; i++) { expect(bucket.consume().allowed).toBe(true); } expect(bucket.consume().allowed).toBe(false); // Refill via time t += 1000; // +10 tokens expect(bucket.consume().allowed).toBe(true); }); it("sliding window per-key isolation", { timeout: 5000 }, async () => { const storage = new MemoryStorage({ clock }); const limiter = createRateLimiter({ defaultConfig: { limit: 2, window: 60_000 }, storage, clock, keyPrefix: "rl", }); // Exhaust key "a:user:1" await limiter.check({ key: "a:user:1", tenantId: "a" }); await limiter.check({ key: "a:user:1", tenantId: "a" }); const r1 = await limiter.check({ key: "a:user:1", tenantId: "a" }); expect(r1.allowed).toBe(false); // "b:user:1" is a separate storage key and is unaffected const r2 = await limiter.check({ key: "b:user:1", tenantId: "b" }); expect(r2.allowed).toBe(true); }); it("fixed window and sliding window agreement on boundary", { timeout: 5000, }, () => { t = 0; const fw = new FixedWindow({ limit: 3, windowMs: 1000, clock }); const sw = new SlidingWindow({ limit: 3, windowMs: 1000, clock }); for (let i = 0; i < 3; i++) { expect(fw.check("k").allowed).toBe(true); expect(sw.check("k").allowed).toBe(true); } // Both reject expect(fw.check("k").allowed).toBe(false); expect(sw.check("k").allowed).toBe(false); // Advance past window t = 1001; expect(fw.check("k").allowed).toBe(true); expect(sw.check("k").allowed).toBe(true); }); it("rate limiter with all rule conditions", { timeout: 5000 }, async () => { const limiter = createRateLimiter({ defaultConfig: { limit: 100, window: 1000 }, rules: [ { id: "ip-block", limit: 0, window: 1000, priority: 90, bypass: false, conditions: [ { field: "ip", operator: "eq", value: "10.0.0.1" }, ], }, { id: "admin-pass", limit: 0, window: 1000, priority: 100, bypass: true, conditions: [ { field: "userId", operator: "in", value: ["admin"] }, ], }, ], clock, }); // Admin bypasses all limits const adminResult = await limiter.check({ key: "admin-key", userId: "admin", }); expect(adminResult.allowed).toBe(true); // Blocked IP hits limit immediately const blockedResult = await limiter.check({ key: "blocked-ip", ip: "10.0.0.1", }); expect(blockedResult.allowed).toBe(false); }); it("getState after multiple checks is consistent", { timeout: 5000, }, async () => { const storage = new MemoryStorage({ clock }); const limiter = createRateLimiter({ defaultConfig: { limit: 5, window: 1000 }, storage, clock, }); await limiter.check({ key: "u1" }); await limiter.check({ key: "u1" }); await limiter.check({ key: "u1" }); const state = await limiter.getState("u1"); expect(state).not.toBeNull(); expect(state?.algorithm).toBe("sliding-window"); expect("requests" in state!.state).toBe(true); if ("requests" in state!.state) { expect(state!.state.requests.length).toBe(3); } }); });