integration.test.ts•12.2 kB
/**
* Integration Tests for GEPA Resilience System
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
ResilienceSystem,
CircuitBreakerFactory,
RetryManager,
GracefulDegradationManager,
TimeoutManager,
executeLLMWithResilience,
executeDatabaseWithResilience,
executeComputationWithResilience
} from './index';
describe('GEPA Resilience System Integration', () => {
let resilience: ResilienceSystem;
beforeEach(() => {
resilience = ResilienceSystem.getInstance();
});
afterEach(async () => {
await resilience.emergencyShutdown('Test cleanup');
});
describe('Full Protection Integration', () => {
it('should handle successful operations with full protection', async () => {
const operation = vi.fn().mockResolvedValue('success');
const result = await resilience.executeWithFullProtection(operation, {
serviceName: 'test-service',
context: {
name: 'test-operation',
priority: 'medium'
}
});
expect(result).toBe('success');
expect(operation).toHaveBeenCalledOnce();
});
it('should apply circuit breaker protection', async () => {
let callCount = 0;
const failingOperation = vi.fn().mockImplementation(() => {
callCount++;
if (callCount <= 5) {
throw new Error('Service failure');
}
return 'success';
});
// Should eventually open circuit breaker
for (let i = 0; i < 3; i++) {
try {
await resilience.executeWithFullProtection(failingOperation, {
serviceName: 'failing-service',
context: { name: 'test', priority: 'low' }
});
} catch (error) {
// Expected failures
}
}
// Circuit should be open now, operation should be rejected without calling
const preCallCount = callCount;
try {
await resilience.executeWithFullProtection(failingOperation, {
serviceName: 'failing-service',
context: { name: 'test', priority: 'low' }
});
} catch (error) {
expect(error.message).toContain('Circuit breaker');
}
// Verify circuit breaker prevented the call
expect(callCount).toBe(preCallCount);
});
it('should apply retry logic with exponential backoff', async () => {
const operation = vi.fn()
.mockRejectedValueOnce(new Error('Temporary failure'))
.mockRejectedValueOnce(new Error('Another temporary failure'))
.mockResolvedValue('success');
const startTime = Date.now();
const result = await resilience.executeWithFullProtection(operation, {
serviceName: 'retry-service',
retryPolicy: 'generic',
context: { name: 'test', priority: 'medium' }
});
const elapsed = Date.now() - startTime;
expect(result).toBe('success');
expect(operation).toHaveBeenCalledTimes(3);
expect(elapsed).toBeGreaterThan(1000); // Should have delays
});
it('should apply timeout protection', async () => {
const slowOperation = vi.fn().mockImplementation(
() => new Promise(resolve => setTimeout(resolve, 10000))
);
await expect(
resilience.executeWithFullProtection(slowOperation, {
serviceName: 'slow-service',
timeoutConfig: 'generic',
context: { name: 'slow-test', priority: 'low' }
})
).rejects.toThrow();
expect(slowOperation).toHaveBeenCalledOnce();
}, 35000);
it('should use fallback values when all else fails', async () => {
const failingOperation = vi.fn().mockRejectedValue(new Error('Total failure'));
const result = await resilience.executeWithFullProtection(failingOperation, {
serviceName: 'fallback-service',
fallbackValue: 'fallback-result',
context: { name: 'test', priority: 'low' }
});
expect(result).toBe('fallback-result');
});
});
describe('Service-Specific Resilience Helpers', () => {
it('should handle LLM operations with appropriate resilience', async () => {
const llmOperation = vi.fn().mockResolvedValue({
content: 'LLM response',
model: 'claude',
tokens: { prompt: 100, completion: 50, total: 150 },
finishReason: 'stop',
latency: 1000,
timestamp: new Date()
});
const result = await executeLLMWithResilience(llmOperation, {
context: { name: 'test-llm', priority: 'high' }
});
expect(result.content).toBe('LLM response');
expect(llmOperation).toHaveBeenCalledOnce();
});
it('should handle database operations with database-specific settings', async () => {
const dbOperation = vi.fn().mockResolvedValue([{ id: 1, data: 'test' }]);
const result = await executeDatabaseWithResilience(dbOperation, {
context: { name: 'db-query', priority: 'medium' }
});
expect(result).toEqual([{ id: 1, data: 'test' }]);
expect(dbOperation).toHaveBeenCalledOnce();
});
it('should handle computations with computation-specific settings', async () => {
const computation = vi.fn().mockResolvedValue(42);
const result = await executeComputationWithResilience(computation, {
context: { name: 'calculation', priority: 'low' }
});
expect(result).toBe(42);
expect(computation).toHaveBeenCalledOnce();
});
});
describe('System Health and Metrics', () => {
it('should provide comprehensive system metrics', async () => {
// Execute some operations to generate metrics
const operation = vi.fn().mockResolvedValue('test');
await resilience.executeWithFullProtection(operation, {
serviceName: 'metrics-test',
context: { name: 'test', priority: 'medium' }
});
const metrics = await resilience.getResilienceMetrics();
expect(metrics).toHaveProperty('circuitBreakers');
expect(metrics).toHaveProperty('retryStats');
expect(metrics).toHaveProperty('systemHealth');
expect(metrics).toHaveProperty('activeTimeouts');
});
it('should provide system status assessment', async () => {
const status = await resilience.getSystemStatus();
expect(status).toHaveProperty('status');
expect(status).toHaveProperty('components');
expect(status).toHaveProperty('metrics');
expect(status).toHaveProperty('recommendations');
expect(['healthy', 'degraded', 'critical']).toContain(status.status);
});
});
describe('Component Integration', () => {
it('should coordinate between circuit breakers and retry manager', async () => {
let attemptCount = 0;
const operation = vi.fn().mockImplementation(() => {
attemptCount++;
if (attemptCount <= 2) {
throw new Error('Retryable error');
}
return 'success';
});
const result = await resilience.executeWithFullProtection(operation, {
serviceName: 'coordination-test',
retryPolicy: 'generic',
circuitBreaker: true,
context: { name: 'test', priority: 'medium' }
});
expect(result).toBe('success');
expect(attemptCount).toBe(3); // 1 initial + 2 retries
});
it('should handle cascading failures gracefully', async () => {
const operations = Array.from({ length: 10 }, (_, i) =>
vi.fn().mockRejectedValue(new Error(`Failure ${i}`))
);
const results = await Promise.allSettled(
operations.map((op, i) =>
resilience.executeWithFullProtection(op, {
serviceName: `cascade-test-${i}`,
fallbackValue: `fallback-${i}`,
context: { name: `test-${i}`, priority: 'low' }
})
)
);
// Should not crash the system
expect(results).toHaveLength(10);
// At least some should use fallback values
const fulfilled = results.filter(r => r.status === 'fulfilled');
expect(fulfilled.length).toBeGreaterThan(0);
});
});
describe('Error Handling', () => {
it('should provide user-friendly error messages', async () => {
const networkError = new Error('ECONNRESET: Connection reset by peer');
const operation = vi.fn().mockRejectedValue(networkError);
try {
await resilience.executeWithFullProtection(operation, {
serviceName: 'error-test',
context: { name: 'test', priority: 'medium' }
});
} catch (error) {
// Should still throw, but with potential circuit breaker context
expect(error).toBeInstanceOf(Error);
}
});
it('should handle abort signals properly', async () => {
const controller = new AbortController();
const operation = vi.fn().mockImplementation(async (signal) => {
// Simulate some work
await new Promise(resolve => setTimeout(resolve, 100));
if (signal?.aborted) {
throw new Error('Operation aborted');
}
return 'success';
});
// Start operation
const promise = resilience.executeWithFullProtection(operation, {
serviceName: 'abort-test',
context: { name: 'test', priority: 'medium' }
});
// Abort immediately
controller.abort();
// Should handle abort gracefully
await expect(promise).rejects.toThrow();
});
});
describe('Performance Under Load', () => {
it('should handle concurrent operations efficiently', async () => {
const operation = vi.fn().mockImplementation(async () => {
await new Promise(resolve => setTimeout(resolve, 10));
return Math.random();
});
const startTime = Date.now();
const promises = Array.from({ length: 20 }, (_, i) =>
resilience.executeWithFullProtection(operation, {
serviceName: `concurrent-test-${i % 3}`, // Mix of services
context: { name: `concurrent-${i}`, priority: 'medium' }
})
);
const results = await Promise.all(promises);
const elapsed = Date.now() - startTime;
expect(results).toHaveLength(20);
expect(elapsed).toBeLessThan(5000); // Should complete reasonably fast
expect(operation).toHaveBeenCalledTimes(20);
});
});
});
describe('Individual Component Integration', () => {
afterEach(async () => {
await CircuitBreakerFactory.cleanup();
await RetryManager.getInstance().cleanup();
await GracefulDegradationManager.getInstance().cleanup();
await TimeoutManager.getInstance().cleanup();
});
it('should integrate circuit breaker with timeout manager', async () => {
const timeoutManager = TimeoutManager.getInstance();
const circuitBreaker = CircuitBreakerFactory.createServiceCircuitBreaker('timeout-test');
const slowOperation = () => new Promise(resolve => setTimeout(resolve, 2000));
// Should timeout and trigger circuit breaker failure
await expect(
timeoutManager.executeWithTimeout(
async () => await circuitBreaker.execute(slowOperation),
'generic',
{ name: 'slow-test', priority: 'low', canAbort: true }
)
).rejects.toThrow();
const metrics = circuitBreaker.getMetrics();
expect(metrics.failureCount).toBeGreaterThan(0);
});
it('should integrate retry manager with graceful degradation', async () => {
const retryManager = RetryManager.getInstance();
const degradationManager = GracefulDegradationManager.getInstance();
let callCount = 0;
const operation = vi.fn().mockImplementation(async () => {
callCount++;
if (callCount <= 2) {
throw new Error('Database connection failed');
}
return 'database result';
});
const result = await degradationManager.executeWithFallback(
'test-db-service',
async () => {
return await retryManager.executeWithRetry(
operation,
'trajectory-store',
{ name: 'db-operation', priority: 'medium', canAbort: true }
);
},
{ name: 'test-context' },
'fallback-data'
);
expect(result).toBe('database result');
expect(callCount).toBe(3); // 1 initial + 2 retries
});
});