Skip to main content
Glama

MCP Console Automation Server

RetryManager.test.ts7.11 kB
/** * Tests for RetryManager */ import { RetryManager, CircuitBreaker } from '../testing/RetryManager.js'; import { TestDefinition, TestResult, RetryConfig, } from '../types/test-framework.js'; describe('RetryManager', () => { let retryManager: RetryManager; beforeEach(() => { retryManager = new RetryManager(); }); describe('Basic Retry Logic', () => { it('should retry failed tests up to maxRetries', async () => { let attemptCount = 0; const test: TestDefinition = { name: 'flaky-test', assertions: [], timeout: 100, retry: 0, }; const executor = async (t: TestDefinition): Promise<TestResult> => { attemptCount++; return { test: t, status: attemptCount < 3 ? 'fail' : 'pass', duration: 10, startTime: Date.now(), endTime: Date.now() + 10, assertions: [], error: attemptCount < 3 ? new Error('Simulated failure') : undefined, }; }; const config: RetryConfig = { maxRetries: 3, backoffMs: 10, }; const result = await retryManager.executeWithRetry( test, executor, config ); expect(result.attempts).toBe(3); expect(result.finalResult.status).toBe('pass'); expect(result.results).toHaveLength(3); }); it('should stop retrying after success', async () => { let attemptCount = 0; const test: TestDefinition = { name: 'test', assertions: [], timeout: 100, retry: 0, }; const executor = async (t: TestDefinition): Promise<TestResult> => { attemptCount++; return { test: t, status: 'pass', duration: 10, startTime: Date.now(), endTime: Date.now() + 10, assertions: [], }; }; const result = await retryManager.executeWithRetry(test, executor, { maxRetries: 5, backoffMs: 10, }); expect(result.attempts).toBe(1); expect(result.finalResult.status).toBe('pass'); }); }); describe('Exponential Backoff', () => { it('should apply exponential backoff between retries', async () => { const timestamps: number[] = []; const test: TestDefinition = { name: 'test', assertions: [], timeout: 100, retry: 0, }; const executor = async (t: TestDefinition): Promise<TestResult> => { timestamps.push(Date.now()); return { test: t, status: 'fail', duration: 0, startTime: Date.now(), endTime: Date.now(), assertions: [], error: new Error('fail'), }; }; const config: RetryConfig = { maxRetries: 3, backoffMs: 100, backoffMultiplier: 2, }; await retryManager.executeWithRetry(test, executor, config); expect(timestamps).toHaveLength(4); // Initial + 3 retries // Check delays are increasing (with some tolerance for timing) const delay1 = timestamps[1] - timestamps[0]; const delay2 = timestamps[2] - timestamps[1]; const delay3 = timestamps[3] - timestamps[2]; expect(delay1).toBeGreaterThanOrEqual(90); expect(delay2).toBeGreaterThanOrEqual(180); expect(delay3).toBeGreaterThanOrEqual(360); }); }); describe('Selective Retry', () => { it('should only retry on specific error types', async () => { let attemptCount = 0; const test: TestDefinition = { name: 'test', assertions: [], timeout: 100, retry: 0, }; const executor = async (t: TestDefinition): Promise<TestResult> => { attemptCount++; return { test: t, status: 'fail', duration: 0, startTime: Date.now(), endTime: Date.now(), assertions: [], error: new Error('ASSERTION_FAILED'), }; }; const config: RetryConfig = { maxRetries: 3, backoffMs: 10, retryOnErrors: ['NETWORK', 'TIMEOUT'], }; const result = await retryManager.executeWithRetry( test, executor, config ); // Should not retry because error doesn't match pattern expect(result.attempts).toBe(1); }); it('should retry on timeout if configured', async () => { let attemptCount = 0; const test: TestDefinition = { name: 'test', assertions: [], timeout: 100, retry: 0, }; const executor = async (t: TestDefinition): Promise<TestResult> => { attemptCount++; return { test: t, status: 'timeout', duration: 0, startTime: Date.now(), endTime: Date.now(), assertions: [], }; }; const config: RetryConfig = { maxRetries: 2, backoffMs: 10, retryOnTimeout: true, }; const result = await retryManager.executeWithRetry( test, executor, config ); expect(result.attempts).toBe(3); }); }); describe('Retry Statistics', () => { it('should generate statistics for retry results', async () => { const failedTests: TestResult[] = [ { test: { name: 'test-1', assertions: [], timeout: 100, retry: 0 }, status: 'fail', duration: 0, startTime: Date.now(), endTime: Date.now(), assertions: [], }, { test: { name: 'test-2', assertions: [], timeout: 100, retry: 0 }, status: 'fail', duration: 0, startTime: Date.now(), endTime: Date.now(), assertions: [], }, ]; const executor = async (t: TestDefinition): Promise<TestResult> => ({ test: t, status: 'pass', duration: 10, startTime: Date.now(), endTime: Date.now() + 10, assertions: [], }); const results = await retryManager.retryFailedTests( failedTests, executor, { maxRetries: 2, backoffMs: 10 } ); const stats = retryManager.getRetryStatistics(results); expect(stats.totalTests).toBe(2); expect(stats.successAfterRetry).toBe(2); expect(stats.successRate).toBe(1); }); }); }); describe('CircuitBreaker', () => { it('should open circuit after threshold failures', () => { const breaker = new CircuitBreaker(3, 60000, 30000); expect(breaker.isOpen()).toBe(false); breaker.recordFailure(); breaker.recordFailure(); expect(breaker.isOpen()).toBe(false); breaker.recordFailure(); expect(breaker.isOpen()).toBe(true); }); it('should close circuit after successful execution', () => { const breaker = new CircuitBreaker(2, 60000, 30000); breaker.recordFailure(); breaker.recordFailure(); expect(breaker.isOpen()).toBe(true); breaker.recordSuccess(); expect(breaker.isOpen()).toBe(false); }); });

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/ooples/mcp-console-automation'

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