atproto user agency toolkit for individuals and groups
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 136 lines 4.0 kB view raw
1import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; 2import { RateLimiter, DEFAULT_RATE_LIMIT_CONFIG } from "./rate-limiter.js"; 3import type { RateLimitRule } from "./rate-limiter.js"; 4 5describe("RateLimiter", () => { 6 let limiter: RateLimiter; 7 8 beforeEach(() => { 9 limiter = new RateLimiter(); 10 }); 11 12 afterEach(() => { 13 limiter.stop(); 14 }); 15 16 describe("check()", () => { 17 const rule: RateLimitRule = { maxRequests: 5, windowMs: 1000 }; 18 19 it("allows requests under the limit", () => { 20 for (let i = 0; i < 5; i++) { 21 const result = limiter.check("test", "ip1", rule); 22 expect(result.allowed).toBe(true); 23 expect(result.remaining).toBe(4 - i); 24 } 25 }); 26 27 it("rejects requests at the limit", () => { 28 for (let i = 0; i < 5; i++) { 29 limiter.check("test", "ip1", rule); 30 } 31 const result = limiter.check("test", "ip1", rule); 32 expect(result.allowed).toBe(false); 33 expect(result.remaining).toBe(0); 34 expect(result.retryAfterMs).toBeGreaterThan(0); 35 }); 36 37 it("isolates different keys", () => { 38 for (let i = 0; i < 5; i++) { 39 limiter.check("test", "ip1", rule); 40 } 41 const result = limiter.check("test", "ip2", rule); 42 expect(result.allowed).toBe(true); 43 }); 44 45 it("isolates different pools", () => { 46 for (let i = 0; i < 5; i++) { 47 limiter.check("pool1", "ip1", rule); 48 } 49 const result = limiter.check("pool2", "ip1", rule); 50 expect(result.allowed).toBe(true); 51 }); 52 53 it("resets after window expires", () => { 54 vi.useFakeTimers(); 55 try { 56 for (let i = 0; i < 5; i++) { 57 limiter.check("test", "ip1", rule); 58 } 59 expect(limiter.check("test", "ip1", rule).allowed).toBe(false); 60 61 // Advance past 2 full windows so previous count is also cleared 62 vi.advanceTimersByTime(2001); 63 64 const result = limiter.check("test", "ip1", rule); 65 expect(result.allowed).toBe(true); 66 expect(result.remaining).toBe(4); 67 } finally { 68 vi.useRealTimers(); 69 } 70 }); 71 72 it("uses sliding window weight for gradual recovery", () => { 73 vi.useFakeTimers(); 74 try { 75 // Fill up the window 76 for (let i = 0; i < 5; i++) { 77 limiter.check("test", "ip1", rule); 78 } 79 expect(limiter.check("test", "ip1", rule).allowed).toBe(false); 80 81 // Advance to 80% through the next window 82 // Previous window count (5) gets weighted by 0.2 = 1.0 effective 83 // So we should have ~4 remaining 84 vi.advanceTimersByTime(1800); 85 86 const result = limiter.check("test", "ip1", rule); 87 expect(result.allowed).toBe(true); 88 } finally { 89 vi.useRealTimers(); 90 } 91 }); 92 }); 93 94 describe("cleanup", () => { 95 it("removes stale entries", () => { 96 vi.useFakeTimers(); 97 try { 98 const rule: RateLimitRule = { maxRequests: 10, windowMs: 1000 }; 99 limiter.check("test", "ip1", rule); 100 limiter.startCleanup(100); 101 102 // Advance past 2 minutes (cleanup threshold) 103 vi.advanceTimersByTime(130_000); 104 105 // Entry should be cleaned up, next check starts fresh 106 const result = limiter.check("test", "ip1", rule); 107 expect(result.allowed).toBe(true); 108 expect(result.remaining).toBe(9); 109 } finally { 110 vi.useRealTimers(); 111 } 112 }); 113 }); 114 115 describe("stop()", () => { 116 it("clears all state", () => { 117 const rule: RateLimitRule = { maxRequests: 1, windowMs: 60_000 }; 118 limiter.check("test", "ip1", rule); 119 limiter.stop(); 120 121 // After stop, state is cleared — new check should succeed 122 const result = limiter.check("test", "ip1", rule); 123 expect(result.allowed).toBe(true); 124 }); 125 }); 126 127 describe("DEFAULT_RATE_LIMIT_CONFIG", () => { 128 it("has expected pools", () => { 129 expect(DEFAULT_RATE_LIMIT_CONFIG.httpUnauthenticated.read.maxRequests).toBe(300); 130 expect(DEFAULT_RATE_LIMIT_CONFIG.httpUnauthenticated.sync.maxRequests).toBe(30); 131 expect(DEFAULT_RATE_LIMIT_CONFIG.httpUnauthenticated.session.maxRequests).toBe(10); 132 expect(DEFAULT_RATE_LIMIT_CONFIG.httpAuthenticated.write.maxRequests).toBe(200); 133 expect(DEFAULT_RATE_LIMIT_CONFIG.firehosePerIp.maxConnections).toBe(3); 134 }); 135 }); 136});