Skip to main content
Glama

Prompt Auto-Optimizer MCP

by sloth-wq
circuit-breaker.test.ts8.47 kB
/** * Circuit Breaker Tests */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { CircuitBreaker, CircuitBreakerState, CircuitBreakerFactory } from './circuit-breaker'; describe('CircuitBreaker', () => { let circuitBreaker: CircuitBreaker; beforeEach(() => { circuitBreaker = new CircuitBreaker({ name: 'test-breaker', failureThreshold: 3, recoveryTimeout: 1000, successThreshold: 2, timeout: 5000, monitoringWindow: 10000, maxRetries: 2, exponentialBackoff: true, jitterEnabled: false }); }); afterEach(async () => { await circuitBreaker.cleanup(); }); describe('Basic Operation', () => { it('should execute successful operations normally', async () => { const operation = vi.fn().mockResolvedValue('success'); const result = await circuitBreaker.execute(operation); expect(result).toBe('success'); expect(operation).toHaveBeenCalledOnce(); }); it('should handle operation failures', async () => { const operation = vi.fn().mockRejectedValue(new Error('Operation failed')); await expect(circuitBreaker.execute(operation)).rejects.toThrow('Operation failed'); }); it('should transition to OPEN state after threshold failures', async () => { const operation = vi.fn().mockRejectedValue(new Error('Failure')); // Fail 3 times to trigger the threshold for (let i = 0; i < 3; i++) { try { await circuitBreaker.execute(operation); } catch (error) { // Expected failures } } const metrics = circuitBreaker.getMetrics(); expect(metrics.state).toBe(CircuitBreakerState.OPEN); }); it('should reject requests when circuit is OPEN', async () => { const operation = vi.fn().mockRejectedValue(new Error('Failure')); // Force circuit to open circuitBreaker.forceState(CircuitBreakerState.OPEN); await expect(circuitBreaker.execute(operation)).rejects.toThrow('Circuit breaker'); expect(operation).not.toHaveBeenCalled(); }); }); describe('State Transitions', () => { it('should transition to HALF_OPEN after recovery timeout', async () => { const operation = vi.fn() .mockRejectedValueOnce(new Error('Failure')) .mockRejectedValueOnce(new Error('Failure')) .mockRejectedValueOnce(new Error('Failure')) .mockResolvedValue('success'); // Force failures to open circuit for (let i = 0; i < 3; i++) { try { await circuitBreaker.execute(operation); } catch (error) { // Expected } } expect(circuitBreaker.getMetrics().state).toBe(CircuitBreakerState.OPEN); // Wait for recovery timeout await new Promise(resolve => setTimeout(resolve, 1100)); // Next call should transition to HALF_OPEN and succeed const result = await circuitBreaker.execute(operation); expect(result).toBe('success'); }); it('should transition to CLOSED after successful recovery', async () => { const operation = vi.fn().mockResolvedValue('success'); // Start in HALF_OPEN state circuitBreaker.forceState(CircuitBreakerState.HALF_OPEN); // Execute successful operations to meet success threshold await circuitBreaker.execute(operation); await circuitBreaker.execute(operation); const metrics = circuitBreaker.getMetrics(); expect(metrics.state).toBe(CircuitBreakerState.CLOSED); }); }); describe('Retry Logic', () => { it('should retry failed operations up to maxRetries', async () => { const operation = vi.fn() .mockRejectedValueOnce(new Error('Temporary failure')) .mockRejectedValueOnce(new Error('Temporary failure')) .mockResolvedValue('success'); const result = await circuitBreaker.execute(operation); expect(result).toBe('success'); expect(operation).toHaveBeenCalledTimes(3); }); it('should respect timeout limits', async () => { const slowOperation = () => new Promise(resolve => setTimeout(resolve, 6000)); await expect(circuitBreaker.execute(slowOperation)).rejects.toThrow('timeout'); }); }); describe('Metrics', () => { it('should track failure and success counts', async () => { const successOp = vi.fn().mockResolvedValue('success'); const failOp = vi.fn().mockRejectedValue(new Error('failure')); await circuitBreaker.execute(successOp); try { await circuitBreaker.execute(failOp); } catch (error) { // Expected } const metrics = circuitBreaker.getMetrics(); expect(metrics.successCount).toBe(1); expect(metrics.failureCount).toBe(1); expect(metrics.totalRequests).toBe(2); }); it('should calculate response time metrics', async () => { const operation = vi.fn().mockResolvedValue('success'); await circuitBreaker.execute(operation); const metrics = circuitBreaker.getMetrics(); expect(metrics.responseTime.average).toBeGreaterThan(0); }); }); describe('Circuit Breaker Factory', () => { afterEach(async () => { await CircuitBreakerFactory.cleanup(); }); it('should create LLM circuit breaker with correct config', () => { const breaker = CircuitBreakerFactory.createLLMCircuitBreaker(); const metrics = breaker.getMetrics(); expect(metrics.state).toBe(CircuitBreakerState.CLOSED); }); it('should return same instance for same name', () => { const breaker1 = CircuitBreakerFactory.createLLMCircuitBreaker('test'); const breaker2 = CircuitBreakerFactory.createLLMCircuitBreaker('test'); expect(breaker1).toBe(breaker2); }); it('should create trajectory circuit breaker with appropriate settings', () => { const breaker = CircuitBreakerFactory.createTrajectoryCircuitBreaker(); const metrics = breaker.getMetrics(); expect(metrics.state).toBe(CircuitBreakerState.CLOSED); }); }); describe('Events', () => { it('should emit success events', async () => { const successHandler = vi.fn(); circuitBreaker.on('success', successHandler); const operation = vi.fn().mockResolvedValue('success'); await circuitBreaker.execute(operation); expect(successHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'test-breaker', state: CircuitBreakerState.CLOSED })); }); it('should emit failure events', async () => { const failureHandler = vi.fn(); circuitBreaker.on('failure', failureHandler); const operation = vi.fn().mockRejectedValue(new Error('Test error')); try { await circuitBreaker.execute(operation); } catch (error) { // Expected } expect(failureHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'test-breaker', error: expect.any(Error) })); }); it('should emit state change events', async () => { const stateChangeHandler = vi.fn(); circuitBreaker.on('stateChange', stateChangeHandler); const operation = vi.fn().mockRejectedValue(new Error('Failure')); // Trigger state change to OPEN for (let i = 0; i < 3; i++) { try { await circuitBreaker.execute(operation); } catch (error) { // Expected } } expect(stateChangeHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'test-breaker', from: 'CLOSED', to: 'OPEN' })); }); }); describe('Edge Cases', () => { it('should handle operations that throw non-Error objects', async () => { const operation = vi.fn().mockRejectedValue('String error'); await expect(circuitBreaker.execute(operation)).rejects.toBe('String error'); }); it('should reset properly', () => { // Force some failures circuitBreaker.forceState(CircuitBreakerState.OPEN); circuitBreaker.reset(); const metrics = circuitBreaker.getMetrics(); expect(metrics.state).toBe(CircuitBreakerState.CLOSED); expect(metrics.failureCount).toBe(0); expect(metrics.successCount).toBe(0); }); }); });

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/sloth-wq/prompt-auto-optimizer-mcp'

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