import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createLoopRateLimiter } from "./loop-rate-limiter.js"; describe("createLoopRateLimiter", () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it("allows messages below the threshold", () => { const limiter = createLoopRateLimiter({ windowMs: 10_000, maxHits: 3 }); limiter.record("conv:1"); limiter.record("conv:1"); expect(limiter.isRateLimited("conv:1")).toBe(false); }); it("rate limits at the threshold", () => { const limiter = createLoopRateLimiter({ windowMs: 10_000, maxHits: 3 }); limiter.record("conv:1"); limiter.record("conv:1"); limiter.record("conv:1"); expect(limiter.isRateLimited("conv:1")).toBe(true); }); it("does not cross-contaminate conversations", () => { const limiter = createLoopRateLimiter({ windowMs: 10_000, maxHits: 2 }); limiter.record("conv:1"); limiter.record("conv:1"); expect(limiter.isRateLimited("conv:1")).toBe(true); expect(limiter.isRateLimited("conv:2")).toBe(false); }); it("resets after the time window expires", () => { const limiter = createLoopRateLimiter({ windowMs: 5_000, maxHits: 2 }); limiter.record("conv:1"); limiter.record("conv:1"); expect(limiter.isRateLimited("conv:1")).toBe(true); vi.advanceTimersByTime(6_000); expect(limiter.isRateLimited("conv:1")).toBe(false); }); it("returns false for unknown conversations", () => { const limiter = createLoopRateLimiter(); expect(limiter.isRateLimited("unknown")).toBe(false); }); });