const { describe, test, expect, beforeEach } = require("@jest/globals"); // Mock config jest.mock("../../config", () => ({ app: { delayMinutes: 5, }, })); // Mock logger jest.mock("../../lib/logger", () => ({ rateLimitPause: jest.fn(), })); const rateLimiter = require("../../lib/rateLimiter"); describe("RateLimiter", () => { beforeEach(() => { jest.clearAllMocks(); rateLimiter.reset(); }); describe("getRandomDelay", () => { test("should return delay within expected range", () => { const delay = rateLimiter.getRandomDelay(); // Base delay is 5 minutes = 300,000ms // With 20% variance and jitter, expect roughly 240,000 to 390,000ms expect(delay).toBeGreaterThan(200000); expect(delay).toBeLessThan(400000); }); test("should return different delays each time (due to randomization)", () => { const delay1 = rateLimiter.getRandomDelay(); const delay2 = rateLimiter.getRandomDelay(); const delay3 = rateLimiter.getRandomDelay(); // Extremely unlikely to be identical due to randomization expect(delay1).not.toBe(delay2); expect(delay2).not.toBe(delay3); }); }); describe("shouldPause", () => { test("should not pause initially", () => { expect(rateLimiter.shouldPause()).toBe(false); }); test("should pause after sending many emails in short time", () => { // Simulate sending 20 emails in 1 hour (exceeds 15/hour limit) rateLimiter.sentCount = 20; rateLimiter.startTime = Date.now() - 60 * 60 * 1000; // 1 hour ago expect(rateLimiter.shouldPause()).toBe(true); }); test("should pause every 50 emails", () => { rateLimiter.sentCount = 50; expect(rateLimiter.shouldPause()).toBe(true); rateLimiter.sentCount = 100; expect(rateLimiter.shouldPause()).toBe(true); }); test("should not pause if under rate limit", () => { // Simulate sending 10 emails in 1 hour (under 15/hour limit) rateLimiter.sentCount = 10; rateLimiter.startTime = Date.now() - 60 * 60 * 1000; // 1 hour ago expect(rateLimiter.shouldPause()).toBe(false); }); }); describe("getPauseDuration", () => { test("should return pause duration around 30 minutes", () => { const duration = rateLimiter.getPauseDuration(); // Base pause is 30 minutes = 1,800,000ms // With randomization, expect 30-40 minutes expect(duration).toBeGreaterThan(30 * 60 * 1000); expect(duration).toBeLessThan(40 * 60 * 1000); }); }); describe("getNextSendDelay", () => { test("should increment sent count", async () => { const initialCount = rateLimiter.sentCount; await rateLimiter.getNextSendDelay(); expect(rateLimiter.sentCount).toBe(initialCount + 1); }); test("should return pause duration when should pause", async () => { // Force a pause condition rateLimiter.sentCount = 49; // Next increment will trigger pause at 50 const delay = await rateLimiter.getNextSendDelay(); // Should be a long pause, not normal delay expect(delay).toBeGreaterThan(30 * 60 * 1000); // More than 30 minutes }); test("should return normal delay when not pausing", async () => { rateLimiter.sentCount = 5; // Low count, won't trigger pause const delay = await rateLimiter.getNextSendDelay(); // Should be normal delay (around 5 minutes with variance) expect(delay).toBeGreaterThan(200000); expect(delay).toBeLessThan(400000); }); }); describe("formatDelay", () => { test("should format milliseconds to readable time", () => { expect(rateLimiter.formatDelay(60000)).toBe("1m 0s"); expect(rateLimiter.formatDelay(90000)).toBe("1m 30s"); expect(rateLimiter.formatDelay(125000)).toBe("2m 5s"); expect(rateLimiter.formatDelay(3661000)).toBe("61m 1s"); }); }); describe("getStats", () => { test("should return correct statistics", () => { rateLimiter.sentCount = 10; rateLimiter.startTime = Date.now() - 60 * 60 * 1000; // 1 hour ago const stats = rateLimiter.getStats(); expect(stats.sentCount).toBe(10); expect(stats.runtime).toBe(60); // 60 minutes expect(stats.averageRate).toBe("10.0"); // 10 emails per hour expect(stats.nextDelay).toMatch(/^\d+m \d+s$/); // Format like "5m 23s" }); test("should handle zero runtime gracefully", () => { rateLimiter.sentCount = 5; rateLimiter.startTime = Date.now(); // Just started const stats = rateLimiter.getStats(); expect(stats.sentCount).toBe(5); expect(stats.runtime).toBe(0); // Average rate should be very high for instant runtime expect(parseFloat(stats.averageRate)).toBeGreaterThan(1000); }); }); describe("reset", () => { test("should reset all counters", () => { rateLimiter.sentCount = 10; rateLimiter.lastSentTime = Date.now() - 60000; rateLimiter.reset(); expect(rateLimiter.sentCount).toBe(0); expect(rateLimiter.lastSentTime).toBeNull(); expect(rateLimiter.startTime).toBeCloseTo(Date.now(), -1); // Within 10ms }); }); });