馃 distributed transcription service thistle.dunkirk.sh
at v0.1.0 2.9 kB view raw
1import { expect, test } from "bun:test"; 2import db from "../db/schema"; 3import { checkRateLimit, cleanupOldAttempts } from "./rate-limit"; 4 5// Clean up before tests 6db.run("DELETE FROM rate_limit_attempts"); 7 8test("allows requests under the limit", () => { 9 const key = "test:allow"; 10 11 const result1 = checkRateLimit(key, 5, 60); 12 expect(result1.allowed).toBe(true); 13 14 const result2 = checkRateLimit(key, 5, 60); 15 expect(result2.allowed).toBe(true); 16 17 const result3 = checkRateLimit(key, 5, 60); 18 expect(result3.allowed).toBe(true); 19}); 20 21test("blocks requests over the limit", () => { 22 const key = "test:block"; 23 24 // Make 5 requests (limit) 25 for (let i = 0; i < 5; i++) { 26 const result = checkRateLimit(key, 5, 60); 27 expect(result.allowed).toBe(true); 28 } 29 30 // 6th request should be blocked 31 const blocked = checkRateLimit(key, 5, 60); 32 expect(blocked.allowed).toBe(false); 33 expect(blocked.retryAfter).toBeGreaterThan(0); 34}); 35 36test("rolling window allows requests after time passes", async () => { 37 const key = "test:rolling"; 38 39 // Make 3 requests 40 for (let i = 0; i < 3; i++) { 41 checkRateLimit(key, 3, 2); // 3 per 2 seconds 42 } 43 44 // 4th should be blocked 45 let result = checkRateLimit(key, 3, 2); 46 expect(result.allowed).toBe(false); 47 48 // Wait for window to pass 49 await new Promise((resolve) => setTimeout(resolve, 2100)); 50 51 // Should now be allowed (old attempts outside window) 52 result = checkRateLimit(key, 3, 2); 53 expect(result.allowed).toBe(true); 54}); 55 56test("different keys are tracked separately", () => { 57 const key1 = "test:separate1"; 58 const key2 = "test:separate2"; 59 60 // Exhaust limit for key1 61 for (let i = 0; i < 5; i++) { 62 checkRateLimit(key1, 5, 60); 63 } 64 65 const blocked = checkRateLimit(key1, 5, 60); 66 expect(blocked.allowed).toBe(false); 67 68 // key2 should still be allowed 69 const allowed = checkRateLimit(key2, 5, 60); 70 expect(allowed.allowed).toBe(true); 71}); 72 73test("cleanup removes old attempts", () => { 74 const key = "test:cleanup"; 75 76 // Create some attempts 77 checkRateLimit(key, 10, 60); 78 checkRateLimit(key, 10, 60); 79 80 // Manually insert an old attempt (25 hours ago) 81 const oldTimestamp = Math.floor(Date.now() / 1000) - 25 * 60 * 60; 82 db.run("INSERT INTO rate_limit_attempts (key, timestamp) VALUES (?, ?)", [ 83 "test:old", 84 oldTimestamp, 85 ]); 86 87 // Run cleanup (default: removes attempts older than 24 hours) 88 cleanupOldAttempts(); 89 90 // Old attempt should be gone 91 const count = db 92 .query<{ count: number }, []>( 93 "SELECT COUNT(*) as count FROM rate_limit_attempts WHERE key = 'test:old'", 94 ) 95 .get(); 96 97 expect(count?.count).toBe(0); 98 99 // Recent attempts should still exist 100 const recentCount = db 101 .query<{ count: number }, [string]>( 102 "SELECT COUNT(*) as count FROM rate_limit_attempts WHERE key = ?", 103 ) 104 .get(key); 105 106 expect(recentCount?.count).toBe(2); 107}); 108 109// Cleanup after tests 110db.run("DELETE FROM rate_limit_attempts");