69 lines · 2.3 KB
import { beforeEach, describe, expect, mock, test } from "bun:test";

const env: Record<string, string> = { PUBLIC_URL: "https://forge.example.com" };
mock.module("cloudflare:workers", () => ({ env }));
mock.module("@tanstack/react-start/server", () => ({ getRequestHeader: () => null }));

const hits = new Map<string, number>();
const deletes: number[] = [];
mock.module("./db", () => ({
  first: async (_sql: string, key: string, window: number) => {
    const id = `${key}@${window}`;
    const next = (hits.get(id) ?? 0) + 1;
    hits.set(id, next);
    return { hits: next };
  },
  run: async (_sql: string, cutoff: number) => {
    deletes.push(cutoff);
  },
}));

const { enforceHourlyLimit, pruneHourlyLimits } = await import("./limits");

const HOUR = 60 * 60 * 1000;
const base = 1_000 * HOUR;

beforeEach(() => {
  hits.clear();
  deletes.length = 0;
  delete env.RATE_LIMITS_DISABLED;
});

describe("enforceHourlyLimit", () => {
  test("allows up to max then rejects with 429", async () => {
    const max = 3;
    for (let i = 0; i < max; i++) await enforceHourlyLimit("sudo:u1", max, base);
    await expect(enforceHourlyLimit("sudo:u1", max, base)).rejects.toMatchObject({ status: 429 });
  });

  test("keys are independent", async () => {
    await enforceHourlyLimit("sudo:u1", 1, base);