Skip to main content
Glama

MCP Gemini Server

by bsmi021
RetryService.test.vitest.ts8.37 kB
// Using vitest globals - see vitest.config.ts globals: true import { RetryService } from "../../../src/utils/RetryService.js"; // Test helper to simulate multiple failures before success function createMultiFailFunction<T>( failures: number, result: T, errorMessage = "Simulated error", errorName = "NetworkError" // Using a retryable error name by default ): () => Promise<T> { let attempts = 0; return async () => { attempts++; if (attempts <= failures) { const error = new Error(errorMessage); error.name = errorName; throw error; } return result; }; } describe("RetryService", () => { // Mock the setTimeout to execute immediately for testing purposes let originalSetTimeout: typeof setTimeout; beforeEach(() => { // Save original setTimeout originalSetTimeout = global.setTimeout; // Replace with a version that executes immediately global.setTimeout = function (fn: TimerHandler): number { if (typeof fn === "function") fn(); return 0; } as typeof setTimeout; }); // Restore setTimeout after tests afterEach(() => { global.setTimeout = originalSetTimeout; }); describe("execute method", () => { let retryService: RetryService; let onRetryMock: ReturnType<typeof vi.fn>; let delaysCollected: number[] = []; beforeEach(() => { delaysCollected = []; onRetryMock = vi.fn( (_error: unknown, _attempt: number, delayMs: number) => { delaysCollected.push(delayMs); } ); retryService = new RetryService({ maxAttempts: 3, initialDelayMs: 10, // Short delay for faster tests maxDelayMs: 50, backoffFactor: 2, jitter: false, // Disable jitter for predictable tests onRetry: onRetryMock, // Force all NetworkError types to be retryable for tests retryableErrorCheck: (err: unknown) => { if (err instanceof Error && err.name === "NetworkError") { return true; } return false; }, }); }); it("should succeed on first attempt", async () => { const fn = vi.fn(async () => "success"); const result = await retryService.execute(fn); expect(result).toBe("success"); expect(fn).toHaveBeenCalledTimes(1); expect(onRetryMock).not.toHaveBeenCalled(); }); it("should retry and succeed after retries", async () => { const fn = createMultiFailFunction(2, "success"); const mockFn = vi.fn(fn); const result = await retryService.execute(mockFn); expect(result).toBe("success"); expect(mockFn).toHaveBeenCalledTimes(3); // 1 initial + 2 retries expect(onRetryMock).toHaveBeenCalledTimes(2); }); it("should throw if max retries are exceeded", async () => { const fn = createMultiFailFunction(5, "never reached"); const mockFn = vi.fn(fn); await expect(retryService.execute(mockFn)).rejects.toThrow( "Simulated error" ); expect(mockFn).toHaveBeenCalledTimes(4); // 1 initial + 3 retries (maxAttempts) expect(onRetryMock).toHaveBeenCalledTimes(3); }); it("should not retry on non-retryable errors", async () => { const error = new Error("Non-retryable error"); error.name = "ValidationError"; // Not in the retryable list const fn = vi.fn(async () => { throw error; }); await expect(retryService.execute(fn)).rejects.toThrow( "Non-retryable error" ); expect(fn).toHaveBeenCalledTimes(1); // No retries expect(onRetryMock).not.toHaveBeenCalled(); }); it("should use custom retryable error check if provided", async () => { const customRetryService = new RetryService({ maxAttempts: 3, initialDelayMs: 10, retryableErrorCheck: (err: unknown) => { return (err as Error).message.includes("custom"); }, }); const nonRetryableFn = vi.fn(async () => { throw new Error("regular error"); // Won't be retried }); const retryableFn = vi.fn(async () => { throw new Error("custom error"); // Will be retried }); // Should not retry for regular error await expect( customRetryService.execute(nonRetryableFn) ).rejects.toThrow(); expect(nonRetryableFn).toHaveBeenCalledTimes(1); // Should retry for custom error await expect(customRetryService.execute(retryableFn)).rejects.toThrow(); expect(retryableFn).toHaveBeenCalledTimes(4); // 1 initial + 3 retries }); }); describe("wrap method", () => { it("should create a function with retry capabilities", async () => { const retryService = new RetryService({ maxAttempts: 2, initialDelayMs: 10, // Ensure errors are retryable in tests retryableErrorCheck: (err: unknown) => { if (err instanceof Error && err.name === "NetworkError") { return true; } return false; }, }); const fn = createMultiFailFunction(1, "success"); const mockFn = vi.fn(fn); const wrappedFn = retryService.wrap(mockFn); const result = await wrappedFn(); expect(result).toBe("success"); expect(mockFn).toHaveBeenCalledTimes(2); // 1 initial + 1 retry }); it("should pass arguments correctly", async () => { const retryService = new RetryService({ maxAttempts: 2 }); const fn = vi.fn(async (a: number, b: string) => { return `${a}-${b}`; }); const wrappedFn = retryService.wrap(fn); const result = await wrappedFn(42, "test"); expect(result).toBe("42-test"); expect(fn).toHaveBeenCalledWith(42, "test"); }); }); describe("withRetry function", () => { // Temporarily create a specialized withRetry for testing const testWithRetry = async function <T>(fn: () => Promise<T>): Promise<T> { const testRetryService = new RetryService({ retryableErrorCheck: (err: unknown) => { if (err instanceof Error && err.name === "NetworkError") { return true; } return false; }, }); return testRetryService.execute(fn); }; it("should retry using default settings", async () => { const fn = createMultiFailFunction(1, "success"); const mockFn = vi.fn(fn); // Use our test-specific function const result = await testWithRetry(mockFn); expect(result).toBe("success"); expect(mockFn).toHaveBeenCalledTimes(2); // 1 initial + 1 retry }); }); describe("delay calculation", () => { it("should use exponential backoff for delays", async () => { const delays: number[] = []; // Create a test-specific RetryService const testRetryService = new RetryService({ maxAttempts: 3, initialDelayMs: 100, maxDelayMs: 1000, backoffFactor: 2, jitter: false, onRetry: (_error: unknown, _attempt: number, delayMs: number) => { delays.push(delayMs); }, }); // Direct access to the private method for testing const delay1 = (testRetryService as any).calculateDelay(0); const delay2 = (testRetryService as any).calculateDelay(1); const delay3 = (testRetryService as any).calculateDelay(2); // Verify calculated delays expect(delay1).toBe(100); expect(delay2).toBe(200); expect(delay3).toBe(400); }); it("should respect maxDelayMs", async () => { // Create a test-specific RetryService with a low maxDelayMs const testRetryService = new RetryService({ maxAttempts: 5, initialDelayMs: 100, maxDelayMs: 300, // Cap at 300ms backoffFactor: 2, jitter: false, }); // Test calculated delays directly const delay1 = (testRetryService as any).calculateDelay(0); const delay2 = (testRetryService as any).calculateDelay(1); const delay3 = (testRetryService as any).calculateDelay(2); // Should be capped const delay4 = (testRetryService as any).calculateDelay(3); // Should be capped // Verify calculated delays expect(delay1).toBe(100); expect(delay2).toBe(200); expect(delay3).toBe(300); // Capped expect(delay4).toBe(300); // Capped }); }); });

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/bsmi021/mcp-gemini-server'

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