diff options
Diffstat (limited to 'packages/plugins/ratelimit-memory/src/index.test.ts')
| -rw-r--r-- | packages/plugins/ratelimit-memory/src/index.test.ts | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/packages/plugins/ratelimit-memory/src/index.test.ts b/packages/plugins/ratelimit-memory/src/index.test.ts new file mode 100644 index 00000000..5bbed769 --- /dev/null +++ b/packages/plugins/ratelimit-memory/src/index.test.ts @@ -0,0 +1,253 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { RateLimiter } from "./index"; + +describe("RateLimiter", () => { + let rateLimiter: RateLimiter; + + beforeEach(() => { + rateLimiter = new RateLimiter(); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + rateLimiter.clear(); + }); + + describe("checkRateLimit", () => { + it("should allow requests within rate limit", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 3, + }; + + const result1 = rateLimiter.checkRateLimit(config, "user1"); + const result2 = rateLimiter.checkRateLimit(config, "user1"); + const result3 = rateLimiter.checkRateLimit(config, "user1"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + expect(result3.allowed).toBe(true); + }); + + it("should block requests exceeding rate limit", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 2, + }; + + const result1 = rateLimiter.checkRateLimit(config, "user1"); + const result2 = rateLimiter.checkRateLimit(config, "user1"); + const result3 = rateLimiter.checkRateLimit(config, "user1"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + expect(result3.allowed).toBe(false); + expect(result3.resetInSeconds).toBeDefined(); + expect(result3.resetInSeconds).toBeGreaterThan(0); + }); + + it("should reset after window expires", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 2, + }; + + // First two requests allowed + const result1 = rateLimiter.checkRateLimit(config, "user1"); + const result2 = rateLimiter.checkRateLimit(config, "user1"); + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + + // Third request blocked + const result3 = rateLimiter.checkRateLimit(config, "user1"); + expect(result3.allowed).toBe(false); + + // Advance time past the window + vi.advanceTimersByTime(61000); + + // Should allow request after window reset + const result4 = rateLimiter.checkRateLimit(config, "user1"); + expect(result4.allowed).toBe(true); + }); + + it("should isolate rate limits by identifier", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + const result1 = rateLimiter.checkRateLimit(config, "user1"); + const result2 = rateLimiter.checkRateLimit(config, "user2"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + }); + + it("should isolate rate limits by key", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + const result1 = rateLimiter.checkRateLimit(config, "user1:/api/v1"); + const result2 = rateLimiter.checkRateLimit(config, "user1:/api/v2"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + }); + + it("should isolate rate limits by config name", () => { + const config1 = { + name: "api", + windowMs: 60000, + maxRequests: 1, + }; + const config2 = { + name: "auth", + windowMs: 60000, + maxRequests: 1, + }; + + const result1 = rateLimiter.checkRateLimit(config1, "user1"); + const result2 = rateLimiter.checkRateLimit(config2, "user1"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + }); + + it("should calculate correct resetInSeconds", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + // First request allowed + rateLimiter.checkRateLimit(config, "user1"); + + // Advance time by 30 seconds + vi.advanceTimersByTime(30000); + + // Second request blocked + const result = rateLimiter.checkRateLimit(config, "user1"); + expect(result.allowed).toBe(false); + // Should have ~30 seconds remaining + expect(result.resetInSeconds).toBeGreaterThan(29); + expect(result.resetInSeconds).toBeLessThanOrEqual(30); + }); + }); + + describe("reset", () => { + it("should reset rate limit for specific identifier", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + // Use up the limit + rateLimiter.checkRateLimit(config, "user1"); + const result1 = rateLimiter.checkRateLimit(config, "user1"); + expect(result1.allowed).toBe(false); + + // Reset the limit + rateLimiter.reset(config, "user1"); + + // Should allow request again + const result2 = rateLimiter.checkRateLimit(config, "user1"); + expect(result2.allowed).toBe(true); + }); + + it("should reset rate limit for specific key", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + // Use up the limit for key1 + rateLimiter.checkRateLimit(config, "user1:/path1"); + const result1 = rateLimiter.checkRateLimit(config, "user1:/path1"); + expect(result1.allowed).toBe(false); + + // Reset only key1 + rateLimiter.reset(config, "user1:/path1"); + + // key1 should be allowed + const result2 = rateLimiter.checkRateLimit(config, "user1:/path1"); + expect(result2.allowed).toBe(true); + }); + + it("should not affect other identifiers", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + // Use up limits for both users + rateLimiter.checkRateLimit(config, "user1"); + rateLimiter.checkRateLimit(config, "user2"); + + // Reset only user1 + rateLimiter.reset(config, "user1"); + + const result1 = rateLimiter.checkRateLimit(config, "user1"); + const result2 = rateLimiter.checkRateLimit(config, "user2"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(false); + }); + }); + + describe("clear", () => { + it("should clear all rate limits", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + // Use up limits for multiple users + rateLimiter.checkRateLimit(config, "user1"); + rateLimiter.checkRateLimit(config, "user2"); + + // Clear all limits + rateLimiter.clear(); + + // All should be allowed + const result1 = rateLimiter.checkRateLimit(config, "user1"); + const result2 = rateLimiter.checkRateLimit(config, "user2"); + + expect(result1.allowed).toBe(true); + expect(result2.allowed).toBe(true); + }); + }); + + describe("cleanup", () => { + it("should cleanup expired entries", () => { + const config = { + name: "test", + windowMs: 60000, + maxRequests: 1, + }; + + // Create an entry + rateLimiter.checkRateLimit(config, "user1"); + + // Advance time past window + cleanup interval + vi.advanceTimersByTime(61000 + 60000); + + // Entry should be cleaned up and new request allowed + const result = rateLimiter.checkRateLimit(config, "user1"); + expect(result.allowed).toBe(true); + }); + }); +}); |
