| import { describe, it, expect, beforeEach } from "bun:test"; |
| import { RateLimitMonitor } from "../src/rate-limit-monitor"; |
| import { OpenAIService } from "../src/openai-service"; |
|
|
| describe("Rate Limit Monitor", () => { |
| let monitor: RateLimitMonitor; |
| let openAIService: OpenAIService; |
|
|
| beforeEach(() => { |
| monitor = new RateLimitMonitor(); |
| openAIService = new OpenAIService(); |
| }); |
|
|
| describe("getCurrentStatus", () => { |
| it("should return rate limit status with additional calculated fields", () => { |
| const status = monitor.getCurrentStatus(); |
|
|
| expect(status).toHaveProperty("requestsInCurrentWindow"); |
| expect(status).toHaveProperty("maxRequestsPerMinute"); |
| expect(status).toHaveProperty("timeUntilWindowReset"); |
| expect(status).toHaveProperty("isCurrentlyLimited"); |
| expect(status).toHaveProperty("recommendedWaitTime"); |
| expect(status).toHaveProperty("utilizationPercentage"); |
| expect(status).toHaveProperty("timeUntilWindowResetMinutes"); |
| expect(status).toHaveProperty("recommendedWaitTimeSeconds"); |
|
|
| expect(typeof status.utilizationPercentage).toBe("number"); |
| expect(status.utilizationPercentage).toBeGreaterThanOrEqual(0); |
| expect(status.utilizationPercentage).toBeLessThanOrEqual(100); |
| }); |
|
|
| it("should calculate utilization percentage correctly", () => { |
| const status = monitor.getCurrentStatus(); |
| const expectedUtilization = |
| (status.requestsInCurrentWindow / status.maxRequestsPerMinute) * 100; |
|
|
| expect(status.utilizationPercentage).toBe(expectedUtilization); |
| }); |
| }); |
|
|
| describe("getRecommendations", () => { |
| it("should return an array of recommendations", () => { |
| const recommendations = monitor.getRecommendations(); |
|
|
| expect(Array.isArray(recommendations)).toBe(true); |
| expect(recommendations.length).toBeGreaterThan(0); |
|
|
| |
| expect(recommendations.some((rec) => rec.includes("batching"))).toBe( |
| true |
| ); |
| expect( |
| recommendations.some((rec) => rec.includes("exponential backoff")) |
| ).toBe(true); |
| expect(recommendations.some((rec) => rec.includes("Monitor"))).toBe(true); |
| }); |
|
|
| it("should provide specific recommendations based on status", () => { |
| const recommendations = monitor.getRecommendations(); |
|
|
| |
| recommendations.forEach((rec) => { |
| expect(typeof rec).toBe("string"); |
| expect(rec.length).toBeGreaterThan(0); |
| }); |
| }); |
| }); |
|
|
| describe("OpenAI Service Rate Limit Integration", () => { |
| it("should expose rate limit status through OpenAI service", () => { |
| const status = openAIService.getRateLimitStatus(); |
|
|
| expect(status).toHaveProperty("requestsInCurrentWindow"); |
| expect(status).toHaveProperty("maxRequestsPerMinute"); |
| expect(status).toHaveProperty("timeUntilWindowReset"); |
| expect(status).toHaveProperty("isCurrentlyLimited"); |
| expect(status).toHaveProperty("recommendedWaitTime"); |
|
|
| expect(typeof status.requestsInCurrentWindow).toBe("number"); |
| expect(typeof status.maxRequestsPerMinute).toBe("number"); |
| expect(typeof status.timeUntilWindowReset).toBe("number"); |
| expect(typeof status.isCurrentlyLimited).toBe("boolean"); |
| expect(typeof status.recommendedWaitTime).toBe("number"); |
| }); |
|
|
| it("should track requests correctly", async () => { |
| const initialStatus = openAIService.getRateLimitStatus(); |
| const initialCount = initialStatus.requestsInCurrentWindow; |
|
|
| |
| const originalChat = openAIService["duckAI"].chat; |
| openAIService["duckAI"].chat = async () => "Mock response"; |
|
|
| try { |
| await openAIService.createChatCompletion({ |
| model: "gpt-4o-mini", |
| messages: [{ role: "user", content: "Test" }], |
| }); |
|
|
| const afterStatus = openAIService.getRateLimitStatus(); |
| expect(afterStatus.requestsInCurrentWindow).toBe(initialCount + 1); |
| } catch (error) { |
| |
| expect(error).toBeInstanceOf(Error); |
| } finally { |
| |
| openAIService["duckAI"].chat = originalChat; |
| } |
| }); |
| }); |
|
|
| describe("Rate Limit Window Management", () => { |
| it("should reset window after time period", () => { |
| const status1 = openAIService.getRateLimitStatus(); |
|
|
| |
| const duckAI = openAIService["duckAI"]; |
|
|
| |
| if (duckAI["rateLimitInfo"]) { |
| duckAI["rateLimitInfo"].windowStart = Date.now() - 61000; |
| } |
|
|
| const status2 = openAIService.getRateLimitStatus(); |
|
|
| |
| expect(status2.requestsInCurrentWindow).toBeLessThanOrEqual( |
| status1.requestsInCurrentWindow |
| ); |
| }); |
|
|
| it("should calculate time until reset correctly", () => { |
| const status = openAIService.getRateLimitStatus(); |
|
|
| expect(status.timeUntilWindowReset).toBeGreaterThanOrEqual(0); |
| expect(status.timeUntilWindowReset).toBeLessThanOrEqual(60000); |
| }); |
| }); |
|
|
| describe("Rate Limit Enforcement", () => { |
| it("should recommend waiting when requests are too frequent", () => { |
| const duckAI = openAIService["duckAI"]; |
|
|
| |
| if (duckAI["rateLimitInfo"]) { |
| duckAI["rateLimitInfo"].lastRequestTime = Date.now() - 500; |
| } |
|
|
| const status = openAIService.getRateLimitStatus(); |
|
|
| |
| expect(status.recommendedWaitTime).toBeGreaterThan(0); |
| }); |
|
|
| it("should detect when rate limit is exceeded", () => { |
| const duckAI = openAIService["duckAI"]; |
|
|
| |
| if (duckAI["rateLimitInfo"]) { |
| const rateLimitInfo = duckAI["rateLimitInfo"]; |
| rateLimitInfo.requestCount = 25; |
| rateLimitInfo.windowStart = Date.now(); |
| rateLimitInfo.isLimited = true; |
| } |
|
|
| |
| const status = openAIService.getRateLimitStatus(); |
|
|
| |
| expect(status.requestsInCurrentWindow).toBeGreaterThan(20); |
| expect(status.isCurrentlyLimited).toBe(true); |
| }); |
| }); |
|
|
| describe("Error Handling", () => { |
| it("should handle rate limit errors gracefully", async () => { |
| |
| const originalChat = openAIService["duckAI"].chat; |
| openAIService["duckAI"].chat = async () => { |
| const error = new Error( |
| "Rate limited. Retry after 60000ms. Status: 429" |
| ); |
| throw error; |
| }; |
|
|
| try { |
| await openAIService.createChatCompletion({ |
| model: "gpt-4o-mini", |
| messages: [{ role: "user", content: "Test" }], |
| }); |
|
|
| |
| expect(true).toBe(false); |
| } catch (error) { |
| expect(error).toBeInstanceOf(Error); |
| expect(error.message).toContain("Rate limited"); |
| } finally { |
| |
| openAIService["duckAI"].chat = originalChat; |
| } |
| }); |
| }); |
|
|
| describe("Monitoring Functions", () => { |
| it("should start and stop monitoring without errors", () => { |
| |
| expect(() => { |
| monitor.startMonitoring(1); |
| monitor.stopMonitoring(); |
| }).not.toThrow(); |
| }); |
|
|
| it("should handle multiple stop calls gracefully", () => { |
| expect(() => { |
| monitor.stopMonitoring(); |
| monitor.stopMonitoring(); |
| }).not.toThrow(); |
| }); |
| }); |
|
|
| describe("Utility Functions", () => { |
| it("should print status without errors", () => { |
| |
| const originalLog = console.log; |
| const logs: string[] = []; |
| console.log = (...args) => logs.push(args.join(" ")); |
|
|
| try { |
| monitor.printStatus(); |
|
|
| |
| expect(logs.length).toBeGreaterThan(0); |
| expect(logs.some((log) => log.includes("Rate Limit Status"))).toBe( |
| true |
| ); |
| } finally { |
| console.log = originalLog; |
| } |
| }); |
|
|
| it("should print recommendations without errors", () => { |
| const originalLog = console.log; |
| const logs: string[] = []; |
| console.log = (...args) => logs.push(args.join(" ")); |
|
|
| try { |
| monitor.printRecommendations(); |
|
|
| expect(logs.length).toBeGreaterThan(0); |
| expect(logs.some((log) => log.includes("Recommendations"))).toBe(true); |
| } finally { |
| console.log = originalLog; |
| } |
| }); |
| }); |
| }); |
|
|