54 lines
1.4 KiB
TypeScript
54 lines
1.4 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { enforceFixedWindowRateLimit } from "@/lib/share/rate-limit-core";
|
|
|
|
function memoryIncrement() {
|
|
const counts = new Map<string, number>();
|
|
return async (key: string) => {
|
|
const count = (counts.get(key) ?? 0) + 1;
|
|
counts.set(key, count);
|
|
return count;
|
|
};
|
|
}
|
|
|
|
describe("share fixed-window rate limits", () => {
|
|
for (const [name, limit] of [
|
|
["sender shares", 10],
|
|
["bot requests", 60],
|
|
["comments", 30],
|
|
] as const) {
|
|
test(`allows ${limit} ${name} and rejects request ${limit + 1}`, async () => {
|
|
const increment = memoryIncrement();
|
|
const options = {
|
|
key: name,
|
|
limit,
|
|
windowSeconds: 3_600,
|
|
increment,
|
|
now: 3_601_000,
|
|
};
|
|
|
|
for (let request = 1; request <= limit; request += 1) {
|
|
await expect(enforceFixedWindowRateLimit(options)).resolves.toBeUndefined();
|
|
}
|
|
await expect(enforceFixedWindowRateLimit(options)).rejects.toMatchObject({
|
|
status: 429,
|
|
retryAfter: 3_599,
|
|
});
|
|
});
|
|
}
|
|
|
|
test("fails closed when Redis is unavailable", async () => {
|
|
await expect(
|
|
enforceFixedWindowRateLimit({
|
|
key: "sender:1",
|
|
limit: 10,
|
|
windowSeconds: 3_600,
|
|
increment: async () => {
|
|
throw new Error("offline");
|
|
},
|
|
}),
|
|
).rejects.toEqual(
|
|
expect.objectContaining({ status: 503 }),
|
|
);
|
|
});
|
|
});
|