Skip to main content
Glama
retry-budget.test.ts8.02 kB
/** * @vitest-environment node */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { RetryBudget, RetryBudgetManager } from '../../../utils/retry/retry-budget.js'; describe('RetryBudget', () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); describe('constructor', () => { it('should create a budget with default configuration', () => { const budget = new RetryBudget(); const stats = budget.getStats(); expect(stats.maximum).toBe(10); // Default from env or fallback expect(stats.consumed).toBe(0); }); it('should create a budget with custom configuration', () => { const budget = new RetryBudget({ maxRetries: 5, timeWindow: 30000 }); const stats = budget.getStats(); expect(stats.maximum).toBe(5); expect(stats.consumed).toBe(0); }); }); describe('canRetry and consumeRetry', () => { it('should allow retries within budget', () => { const budget = new RetryBudget({ maxRetries: 3 }); expect(budget.canRetry()).toBe(true); expect(budget.consumeRetry()).toBe(true); expect(budget.consumeRetry()).toBe(true); expect(budget.consumeRetry()).toBe(true); // Budget exhausted expect(budget.canRetry()).toBe(false); expect(budget.consumeRetry()).toBe(false); }); it('should reset budget after time window', () => { const budget = new RetryBudget({ maxRetries: 2, timeWindow: 1000 }); // Consume all retries expect(budget.consumeRetry()).toBe(true); expect(budget.consumeRetry()).toBe(true); expect(budget.canRetry()).toBe(false); // Advance time past the window vi.advanceTimersByTime(1001); // Budget should be available again expect(budget.canRetry()).toBe(true); expect(budget.consumeRetry()).toBe(true); }); }); describe('getStats', () => { it('should return correct statistics', () => { const budget = new RetryBudget({ maxRetries: 5, timeWindow: 60000 }); budget.consumeRetry(); budget.consumeRetry(); const stats = budget.getStats(); expect(stats.consumed).toBe(2); expect(stats.maximum).toBe(5); expect(stats.remaining).toBe(3); expect(stats.isExhausted).toBe(false); expect(stats.consumedPercentage).toBe(40); }); it('should calculate reset time correctly', () => { const budget = new RetryBudget({ maxRetries: 5, timeWindow: 10000 }); budget.consumeRetry(); const stats = budget.getStats(); expect(stats.resetInMs).toBeGreaterThan(0); expect(stats.resetInMs).toBeLessThanOrEqual(10000); }); it('should show exhausted state', () => { const budget = new RetryBudget({ maxRetries: 1 }); budget.consumeRetry(); const stats = budget.getStats(); expect(stats.isExhausted).toBe(true); expect(stats.remaining).toBe(0); expect(stats.consumedPercentage).toBe(100); }); }); describe('reset', () => { it('should reset the budget', () => { const budget = new RetryBudget({ maxRetries: 3 }); budget.consumeRetry(); budget.consumeRetry(); expect(budget.getStats().consumed).toBe(2); budget.reset(); expect(budget.getStats().consumed).toBe(0); expect(budget.canRetry()).toBe(true); }); }); }); describe('RetryBudgetManager', () => { let manager: RetryBudgetManager; beforeEach(() => { manager = RetryBudgetManager.getInstance(); manager.clear(); vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); describe('singleton pattern', () => { it('should return the same instance', () => { const instance1 = RetryBudgetManager.getInstance(); const instance2 = RetryBudgetManager.getInstance(); expect(instance1).toBe(instance2); }); }); describe('getBudget', () => { it('should create and retrieve budgets by endpoint', () => { const budget1 = manager.getBudget('endpoint1'); const budget2 = manager.getBudget('endpoint2'); const budget1Again = manager.getBudget('endpoint1'); expect(budget1).toBe(budget1Again); expect(budget1).not.toBe(budget2); }); it('should accept custom configuration', () => { const budget = manager.getBudget('custom', { maxRetries: 20 }); const stats = budget.getStats(); expect(stats.maximum).toBe(20); }); }); describe('getGlobalBudget', () => { it('should return the global budget', () => { const global1 = manager.getGlobalBudget(); const global2 = manager.getGlobalBudget(); expect(global1).toBe(global2); }); }); describe('canRetry and consumeRetry', () => { it('should check both endpoint and global budgets', () => { const endpoint = 'test-endpoint'; // Both budgets have capacity expect(manager.canRetry(endpoint)).toBe(true); expect(manager.consumeRetry(endpoint)).toBe(true); }); it('should block when global budget is exhausted', () => { const global = manager.getGlobalBudget(); const endpoint = 'test-endpoint'; // Exhaust global budget while (global.canRetry()) { global.consumeRetry(); } // Should not allow retry even if endpoint budget has capacity expect(manager.canRetry(endpoint)).toBe(false); expect(manager.consumeRetry(endpoint)).toBe(false); }); it('should block when endpoint budget is exhausted', () => { const endpoint = 'test-endpoint'; const budget = manager.getBudget(endpoint, { maxRetries: 1 }); // Exhaust endpoint budget budget.consumeRetry(); // Should not allow retry even if global budget has capacity expect(manager.canRetry(endpoint)).toBe(false); expect(manager.consumeRetry(endpoint)).toBe(false); }); }); describe('getAllStats', () => { it('should return statistics for all budgets', () => { // Create some budgets manager.getBudget('endpoint1').consumeRetry(); manager.getBudget('endpoint2').consumeRetry(); manager.getBudget('endpoint2').consumeRetry(); manager.getGlobalBudget().consumeRetry(); const allStats = manager.getAllStats(); // Should have global + 2 endpoint budgets expect(allStats.size).toBe(3); expect(allStats.has('global')).toBe(true); expect(allStats.has('endpoint1')).toBe(true); expect(allStats.has('endpoint2')).toBe(true); // Check consumed counts expect(allStats.get('global')?.consumed).toBe(1); expect(allStats.get('endpoint1')?.consumed).toBe(1); expect(allStats.get('endpoint2')?.consumed).toBe(2); }); }); describe('resetAll', () => { it('should reset all budgets', () => { // Create and consume from multiple budgets manager.getBudget('endpoint1').consumeRetry(); manager.getBudget('endpoint2').consumeRetry(); manager.getGlobalBudget().consumeRetry(); // Verify they have consumed retries expect(manager.getAllStats().get('global')?.consumed).toBe(1); expect(manager.getAllStats().get('endpoint1')?.consumed).toBe(1); // Reset all manager.resetAll(); // All should be reset const stats = manager.getAllStats(); expect(stats.get('global')?.consumed).toBe(0); expect(stats.get('endpoint1')?.consumed).toBe(0); expect(stats.get('endpoint2')?.consumed).toBe(0); }); }); describe('clear', () => { it('should clear all endpoint budgets and reset global', () => { // Create some budgets manager.getBudget('endpoint1'); manager.getBudget('endpoint2'); manager.getGlobalBudget().consumeRetry(); // Clear manager.clear(); // Should only have global budget const stats = manager.getAllStats(); expect(stats.size).toBe(1); expect(stats.has('global')).toBe(true); expect(stats.get('global')?.consumed).toBe(0); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sapientpants/deepsource-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server