rateLimiter.unit.test.ts
⎇
Raw
1import { describe, expect, test } from "bun:test";
2import config from "../src/config.ts";
3import { checkRateLimit, getClientIp } from "../src/lib/rateLimiter.ts";
4
5describe("checkRateLimit", () => {
6 test("allows up to the limit, then blocks", () => {
7 const ip = `1.1.1.${Math.random()}`;
8 for (let i = 0; i < 3; i++) {
9 expect(checkRateLimit(ip, "login", 3, 60_000)).toBe(true);
10 }
11 expect(checkRateLimit(ip, "login", 3, 60_000)).toBe(false);
12 });
13
14 test("buckets are per kind", () => {
15 const ip = `2.2.2.${Math.random()}`;
16 expect(checkRateLimit(ip, "login", 1, 60_000)).toBe(true);
17 expect(checkRateLimit(ip, "login", 1, 60_000)).toBe(false);
18 expect(checkRateLimit(ip, "comment", 1, 60_000)).toBe(true);
19 });
20
21 test("buckets are per key", () => {
22 const kind = "reaction" as const;
23 expect(checkRateLimit("3.3.3.1", kind, 1, 60_000)).toBe(true);
24 expect(checkRateLimit("3.3.3.1", kind, 1, 60_000)).toBe(false);
25 expect(checkRateLimit("3.3.3.2", kind, 1, 60_000)).toBe(true);
26 });
27
28 test("the window resets", async () => {
29 const ip = `4.4.4.${Math.random()}`;
30 expect(checkRateLimit(ip, "upload", 1, 20)).toBe(true);
31 expect(checkRateLimit(ip, "upload", 1, 20)).toBe(false);
32 await Bun.sleep(40);
33 expect(checkRateLimit(ip, "upload", 1, 20)).toBe(true);
34 });
35
36 test("counting stays correct past the old 1000-call sweep threshold", () => {
37 const ip = `5.5.5.${Math.random()}`;
38 for (let i = 0; i < 1200; i++) {
39 checkRateLimit(`${ip}-${i}`, "register", 5, 60_000);
40 }
41 for (let i = 0; i < 5; i++) {
42 expect(checkRateLimit(ip, "register", 5, 60_000)).toBe(true);
43 }
44 expect(checkRateLimit(ip, "register", 5, 60_000)).toBe(false);
45 });
46
47 test("a null key is always allowed", () => {
48 expect(checkRateLimit(null, "login", 0, 60_000)).toBe(true);
49 });
50
51 test("RATE_LIMIT_DISABLED short-circuits", () => {
52 const saved = config.RATE_LIMIT_DISABLED;
53 config.RATE_LIMIT_DISABLED = true;
54 try {
55 const ip = `6.6.6.${Math.random()}`;
56 for (let i = 0; i < 10; i++) {
57 expect(checkRateLimit(ip, "login", 1, 60_000)).toBe(true);
58 }
59 } finally {
60 config.RATE_LIMIT_DISABLED = saved;
61 }
62 });
63});
64
65describe("getClientIp", () => {
66 test("uses X-Forwarded-For only when TRUSTED_PROXY is set", () => {
67 const req = new Request("http://x/", {
68 headers: { "x-forwarded-for": "9.9.9.9, 10.0.0.1" },
69 });
70 const saved = config.TRUSTED_PROXY;
71 try {
72 config.TRUSTED_PROXY = true;
73 expect(getClientIp(req, null)).toBe("9.9.9.9");
74 config.TRUSTED_PROXY = false;
75 expect(getClientIp(req, null)).toBeNull();
76 } finally {
77 config.TRUSTED_PROXY = saved;
78 }
79 });
80});
81