Spaces:
Paused
Paused
| /** | |
| * ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β AUTONOMOUS SYSTEM INTEGRATION TESTS β | |
| * β β | |
| * β Comprehensive test suite for the autonomous MCP routing system. β | |
| * β Covers: β | |
| * β β’ PatternMemory persistence β | |
| * β β’ FailureMemory recording and recovery paths β | |
| * β β’ DecisionEngine source scoring β | |
| * β β’ AutonomousAgent routing and learning β | |
| * β β’ SelfHealing circuit breaker β | |
| * β β’ API endpoint validation β | |
| * ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| */ | |
| import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest'; | |
| import { initializeDatabase, getDatabase, getSqlJsDatabase } from '../database/index.js'; | |
| import { SqlJsStorageAdapter } from '../mcp/memory/StorageAdapter.js'; | |
| // Mock Embeddings to avoid ONNX/Float32Array issues in tests | |
| vi.mock('../platform/embeddings/TransformersEmbeddings', () => ({ | |
| getTransformersEmbeddings: () => ({ | |
| embed: vi.fn().mockResolvedValue(new Array(384).fill(0.1)), // Mock 384-dim vector | |
| embedBatch: vi.fn().mockImplementation(async (texts) => texts.map(() => new Array(384).fill(0.1))), | |
| initialize: vi.fn().mockResolvedValue(undefined), | |
| isInitialized: vi.fn().mockReturnValue(true), | |
| }) | |
| })); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // TEST SETUP | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Mock Prisma to avoid DATABASE_URLRequirement | |
| vi.mock('../database/prisma', () => ({ | |
| prisma: { | |
| $connect: vi.fn(), | |
| $disconnect: vi.fn(), | |
| $queryRaw: vi.fn().mockResolvedValue([]), | |
| widgetPermission: { | |
| findUnique: vi.fn(), | |
| upsert: vi.fn(), | |
| findMany: vi.fn(), | |
| } | |
| }, | |
| checkPrismaConnection: vi.fn().mockResolvedValue(false), | |
| disconnectPrisma: vi.fn(), | |
| default: { | |
| $connect: vi.fn(), | |
| $disconnect: vi.fn(), | |
| } | |
| })); | |
| let db: any; | |
| beforeAll(async () => { | |
| // Initialize test database | |
| await initializeDatabase(); | |
| // Use raw SqlJs database for Memory components that require .exec() | |
| db = getSqlJsDatabase(); | |
| if (!db) { | |
| throw new Error('Failed to initialize SqlJs database for testing'); | |
| } | |
| // Force clean tables | |
| try { | |
| db.exec('DROP TABLE IF EXISTS mcp_failure_memory'); | |
| db.exec('DROP TABLE IF EXISTS mcp_query_patterns'); | |
| } catch (e) { /* ignore */ } | |
| }); | |
| afterAll(async () => { | |
| // Cleanup would go here | |
| vi.clearAllMocks(); | |
| }); | |
| afterEach(() => { | |
| vi.clearAllMocks(); | |
| }); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // PATTERN MEMORY TESTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe('PatternMemory - Enterprise Persistence', () => { | |
| let patternMemory: any; | |
| beforeEach(async () => { | |
| const { PatternMemory } = await import('../mcp/memory/PatternMemory.js'); | |
| patternMemory = new PatternMemory(new SqlJsStorageAdapter(db)); | |
| }); | |
| it('should record query patterns', async () => { | |
| await patternMemory.recordQuery({ | |
| widgetId: 'test-widget', | |
| queryType: 'agents.list', | |
| queryParams: { limit: 10 }, | |
| sourceUsed: 'test-source', | |
| latencyMs: 50, | |
| resultSize: 1024, | |
| success: true | |
| }); | |
| // Pattern should be in cache immediately | |
| const similar = await patternMemory.findSimilarQueries('agents.list', { limit: 10 }); | |
| const cache = (patternMemory as any).cache; | |
| const genSig = (patternMemory as any).generateSignature('agents.list', { limit: 10 }); | |
| const debugInfo = { | |
| cacheLen: cache.length, | |
| firstSig: cache[0]?.querySignature, | |
| expectedSig: genSig, | |
| matches: cache[0]?.querySignature === genSig, | |
| firstItem: cache[0] | |
| }; | |
| expect(similar.length).toBeGreaterThan(0); | |
| expect(similar[0].pattern.widgetId).toBe('test-widget'); | |
| }); | |
| it('should calculate average latency correctly', async () => { | |
| // Record multiple patterns with different latencies | |
| for (let i = 0; i < 5; i++) { | |
| await patternMemory.recordQuery({ | |
| widgetId: 'latency-test', | |
| queryType: 'test.query', | |
| queryParams: {}, | |
| sourceUsed: 'latency-source', | |
| latencyMs: (i + 1) * 20, // 20, 40, 60, 80, 100 | |
| success: true | |
| }); | |
| } | |
| const avgLatency = await patternMemory.getAverageLatency('latency-source'); | |
| expect(avgLatency).toBe(60); // Average of 20, 40, 60, 80, 100 | |
| }); | |
| it('should calculate success rate correctly', async () => { | |
| // Record 4 successful, 1 failed | |
| for (let i = 0; i < 5; i++) { | |
| await patternMemory.recordQuery({ | |
| widgetId: 'success-test', | |
| queryType: 'success.query', | |
| queryParams: {}, | |
| sourceUsed: 'success-source', | |
| latencyMs: 50, | |
| success: i < 4 // First 4 succeed, last fails | |
| }); | |
| } | |
| const successRate = await patternMemory.getSuccessRate('success-source', 'success.query'); | |
| expect(successRate).toBe(0.8); // 4 out of 5 | |
| }); | |
| it('should provide widget usage patterns', async () => { | |
| // Record patterns for a widget using multiple sources | |
| await patternMemory.recordQuery({ | |
| widgetId: 'multi-source-widget', | |
| queryType: 'data.fetch', | |
| queryParams: {}, | |
| sourceUsed: 'primary-source', | |
| latencyMs: 30, | |
| success: true | |
| }); | |
| await patternMemory.recordQuery({ | |
| widgetId: 'multi-source-widget', | |
| queryType: 'data.fetch', | |
| queryParams: {}, | |
| sourceUsed: 'primary-source', | |
| latencyMs: 40, | |
| success: true | |
| }); | |
| await patternMemory.recordQuery({ | |
| widgetId: 'multi-source-widget', | |
| queryType: 'data.fetch', | |
| queryParams: {}, | |
| sourceUsed: 'secondary-source', | |
| latencyMs: 100, | |
| success: true | |
| }); | |
| const patterns = await patternMemory.getWidgetPatterns('multi-source-widget'); | |
| expect(patterns.widgetId).toBe('multi-source-widget'); | |
| expect(patterns.queryCount).toBe(3); | |
| expect(patterns.commonSources[0]).toBe('primary-source'); // Most used | |
| expect(patterns.successRate).toBe(1.0); | |
| }); | |
| it('should provide comprehensive statistics', async () => { | |
| const stats = await patternMemory.getStatistics(); | |
| expect(stats).toHaveProperty('totalPatterns'); | |
| expect(stats).toHaveProperty('uniqueWidgets'); | |
| expect(stats).toHaveProperty('uniqueSources'); | |
| expect(stats).toHaveProperty('avgLatencyMs'); | |
| expect(stats).toHaveProperty('successRate'); | |
| expect(stats).toHaveProperty('queriesLast24h'); | |
| expect(stats).toHaveProperty('queriesLast7d'); | |
| expect(stats).toHaveProperty('topSources'); | |
| expect(stats).toHaveProperty('topWidgets'); | |
| }); | |
| }); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // FAILURE MEMORY TESTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe('FailureMemory - Self-Healing Intelligence', () => { | |
| let failureMemory: any; | |
| beforeEach(async () => { | |
| const { FailureMemory } = await import('../mcp/memory/FailureMemory.js'); | |
| failureMemory = new FailureMemory(new SqlJsStorageAdapter(db)); | |
| }); | |
| it('should record failures with context', async () => { | |
| const error = new Error('Connection refused'); | |
| (error as any).code = 'ECONNREFUSED'; | |
| const id = await failureMemory.recordFailure({ | |
| sourceName: 'failing-source', | |
| error, | |
| queryContext: { operation: 'test' } | |
| }); | |
| expect(id).toBeDefined(); | |
| expect(typeof id).toBe('string'); | |
| const history = await failureMemory.getFailureHistory('failing-source', 1); | |
| expect(history.length).toBe(1); | |
| expect(history[0].errorType).toBe('Error'); | |
| expect(history[0].errorMessage).toBe('Connection refused'); | |
| expect(history[0].errorContext.code).toBe('ECONNREFUSED'); | |
| }); | |
| it('should track recovery paths', async () => { | |
| const error = new Error('Timeout'); | |
| // Record failure with recovery | |
| await failureMemory.recordFailure({ | |
| sourceName: 'recovery-test', | |
| error, | |
| recoveryAction: 'retry-with-backoff', | |
| recoverySuccess: true, | |
| recoveryTimeMs: 500 | |
| }); | |
| await failureMemory.recordFailure({ | |
| sourceName: 'recovery-test', | |
| error, | |
| recoveryAction: 'retry-with-backoff', | |
| recoverySuccess: true, | |
| recoveryTimeMs: 600 | |
| }); | |
| await failureMemory.recordFailure({ | |
| sourceName: 'recovery-test', | |
| error, | |
| recoveryAction: 'use-fallback', | |
| recoverySuccess: false, | |
| recoveryTimeMs: 1000 | |
| }); | |
| const paths = await failureMemory.getRecoveryPaths('recovery-test', 'Error'); | |
| expect(paths.length).toBeGreaterThan(0); | |
| // retry-with-backoff should be ranked higher (100% success) | |
| const retryPath = paths.find((p: any) => p.action === 'retry-with-backoff'); | |
| expect(retryPath).toBeDefined(); | |
| expect(retryPath.successRate).toBe(1.0); | |
| expect(retryPath.occurrences).toBe(2); | |
| }); | |
| it('should detect recurring failures', async () => { | |
| const error = new Error('Rate limited'); | |
| // Record 3 failures within a short time | |
| for (let i = 0; i < 3; i++) { | |
| await failureMemory.recordFailure({ | |
| sourceName: 'recurring-source', | |
| error | |
| }); | |
| } | |
| const isRecurring = await failureMemory.isRecurringFailure('recurring-source', 'Error', 60); | |
| expect(isRecurring).toBe(true); | |
| }); | |
| it('should recommend best recovery action', async () => { | |
| const error = new Error('DB connection lost'); | |
| // Create a history of recoveries | |
| for (let i = 0; i < 3; i++) { | |
| await failureMemory.recordFailure({ | |
| sourceName: 'db-source', | |
| error, | |
| recoveryAction: 'reconnect', | |
| recoverySuccess: true, | |
| recoveryTimeMs: 200 | |
| }); | |
| } | |
| await failureMemory.recordFailure({ | |
| sourceName: 'db-source', | |
| error, | |
| recoveryAction: 'restart-pool', | |
| recoverySuccess: false, | |
| recoveryTimeMs: 5000 | |
| }); | |
| const best = await failureMemory.getBestRecoveryAction('db-source', 'Error'); | |
| expect(best).toBeDefined(); | |
| expect(best?.action).toBe('reconnect'); | |
| expect(best?.successRate).toBe(1.0); | |
| }); | |
| it('should provide source health summary', async () => { | |
| const summary = await failureMemory.getSourceHealthSummary('db-source'); | |
| expect(summary).toHaveProperty('sourceName'); | |
| expect(summary).toHaveProperty('totalFailures'); | |
| expect(summary).toHaveProperty('uniqueErrorTypes'); | |
| expect(summary).toHaveProperty('recoverySuccessRate'); | |
| expect(summary).toHaveProperty('isRecurring'); | |
| }); | |
| }); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // DECISION ENGINE TESTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe('DecisionEngine - AI-Powered Source Selection', () => { | |
| let decisionEngine: any; | |
| let cognitiveMemory: any; | |
| beforeEach(async () => { | |
| const { initCognitiveMemory, getCognitiveMemory } = await import('../mcp/memory/CognitiveMemory.js'); | |
| const { DecisionEngine } = await import('../mcp/autonomous/DecisionEngine.js'); | |
| initCognitiveMemory(db); | |
| cognitiveMemory = getCognitiveMemory(); | |
| decisionEngine = new DecisionEngine(cognitiveMemory); | |
| }); | |
| it('should analyze query intent correctly', async () => { | |
| const intent = await decisionEngine.analyzeIntent({ | |
| type: 'agents.list', | |
| priority: 'high', | |
| freshness: 'realtime' | |
| }); | |
| expect(intent.type).toBe('agents.list'); | |
| expect(intent.domain).toBe('agents'); | |
| expect(intent.operation).toBe('list'); | |
| expect(intent.priority).toBe('high'); | |
| expect(intent.freshness).toBe('realtime'); | |
| }); | |
| it('should score sources with reasoning', async () => { | |
| const mockSources = [ | |
| { | |
| name: 'fast-source', | |
| type: 'database', | |
| capabilities: ['agents.list', 'agents.get'], | |
| isHealthy: async () => true, | |
| estimatedLatency: 20, | |
| costPerQuery: 0 | |
| }, | |
| { | |
| name: 'slow-source', | |
| type: 'api', | |
| capabilities: ['agents.*'], | |
| isHealthy: async () => true, | |
| estimatedLatency: 500, | |
| costPerQuery: 0.01 | |
| } | |
| ]; | |
| const intent = { | |
| type: 'agents.list', | |
| domain: 'agents', | |
| operation: 'list', | |
| params: {}, | |
| priority: 'high' as const, | |
| freshness: 'normal' as const | |
| }; | |
| const scores = await decisionEngine.scoreAllSources(mockSources, intent); | |
| expect(scores.length).toBe(2); | |
| expect(scores[0].source.name).toBe('fast-source'); // Should be ranked first | |
| expect(scores[0].score).toBeGreaterThan(scores[1].score); | |
| expect(scores[0].reasoning).toBeDefined(); | |
| }); | |
| it('should make confident decisions', async () => { | |
| const mockSources = [ | |
| { | |
| name: 'primary', | |
| type: 'database', | |
| capabilities: ['*'], | |
| isHealthy: async () => true, | |
| estimatedLatency: 50, | |
| costPerQuery: 0 | |
| } | |
| ]; | |
| const intent = { | |
| type: 'test.query', | |
| domain: 'test', | |
| operation: 'query', | |
| params: {}, | |
| priority: 'normal' as const, | |
| freshness: 'normal' as const | |
| }; | |
| const decision = await decisionEngine.decide(mockSources, intent); | |
| expect(decision.selectedSource).toBeDefined(); | |
| expect(decision.selectedSource.name).toBe('primary'); | |
| expect(decision.confidence).toBeGreaterThan(0); | |
| expect(decision.confidence).toBeLessThanOrEqual(1); | |
| expect(decision.reasoning).toBeDefined(); | |
| expect(decision.alternatives).toBeInstanceOf(Array); | |
| }); | |
| }); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SELF-HEALING ADAPTER TESTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe('SelfHealingAdapter - Circuit Breaker & Recovery', () => { | |
| let selfHealing: any; | |
| beforeEach(async () => { | |
| const { selfHealing: sh } = await import('../services/SelfHealingAdapter.js'); | |
| selfHealing = sh; | |
| }); | |
| it('should have default healing strategies', () => { | |
| // The adapter should initialize with default strategies | |
| expect(selfHealing).toBeDefined(); | |
| }); | |
| it('should attempt healing and return result', async () => { | |
| const error = new Error('Test Error'); | |
| const selfHealing = (await import('../services/SelfHealingAdapter.js')).selfHealing; | |
| // Mock the internal logic if needed, or rely on integration | |
| // Since we are testing integration, we expect actual behavior | |
| // But heal returns HealingResult object now? | |
| const healed = await selfHealing.heal(error, 'test-context', { source: 'test-source' }); | |
| expect(healed).toHaveProperty('healed'); | |
| expect(healed).toHaveProperty('strategyUsed'); | |
| }); | |
| it('should prevent recursive healing loops', async () => { | |
| const error = new Error('Recursive Error'); | |
| const selfHealing = (await import('../services/SelfHealingAdapter.js')).selfHealing; | |
| const first = await selfHealing.heal(error, 'recursion-test', { source: 'test-source' }); | |
| // If we somehow trigger during healing, it should handle gracefully | |
| expect(first).toBeDefined(); | |
| }); | |
| it('should provide system status', async () => { | |
| const selfHealing = (await import('../services/SelfHealingAdapter.js')).selfHealing; | |
| const status = selfHealing.getSystemStatus(); | |
| expect(status).toHaveProperty('overallHealth'); | |
| expect(status).toHaveProperty('services'); | |
| expect(status).toHaveProperty('uptime'); | |
| expect(['HEALTHY', 'DEGRADED', 'CRITICAL']).toContain(status.overallHealth); | |
| }); | |
| // it('should update service status', () => { | |
| // selfHealing.updateServiceStatus('test-service', 'healthy'); | |
| // const status = selfHealing.getSystemStatus(); | |
| // expect(status.services).toContainEqual({ name: 'test-service', status: 'healthy' }); | |
| // }); | |
| // const testService = status.services.find((s: any) => s.name === 'test-service'); | |
| // expect(testService).toBeDefined(); | |
| // expect(testService?.status).toBe('healthy'); | |
| }); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // AUTONOMOUS AGENT TESTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe('AutonomousAgent - Orchestration & Learning', () => { | |
| let agent: any; | |
| let registry: any; | |
| beforeEach(async () => { | |
| const { initCognitiveMemory, getCognitiveMemory } = await import('../mcp/memory/CognitiveMemory.js'); | |
| const { AutonomousAgent } = await import('../mcp/autonomous/AutonomousAgent.js'); | |
| const { getSourceRegistry } = await import('../mcp/SourceRegistry.js'); | |
| initCognitiveMemory(db); | |
| const memory = getCognitiveMemory(); | |
| registry = getSourceRegistry(); | |
| // Register a test source | |
| registry.registerSource({ | |
| name: 'test-agent-source', | |
| type: 'test', | |
| capabilities: ['test.*'], | |
| isHealthy: async () => true, | |
| estimatedLatency: 50, | |
| costPerQuery: 0, | |
| query: async () => ({ result: 'test-data' }) | |
| }); | |
| agent = new AutonomousAgent(memory, registry); | |
| }); | |
| it('should route queries to appropriate sources', async () => { | |
| const source = await agent.route({ | |
| type: 'test.query', | |
| domain: 'test', | |
| operation: 'query', | |
| params: {} | |
| }); | |
| expect(source).toBeDefined(); | |
| expect(source.name).toBe('test-agent-source'); | |
| }); | |
| it('should execute and learn from queries', async () => { | |
| const result = await agent.executeAndLearn( | |
| { | |
| type: 'test.query', | |
| widgetId: 'agent-test-widget' | |
| }, | |
| async (source: any) => { | |
| return { data: 'executed-result', source: source.name }; | |
| } | |
| ); | |
| expect(result).toBeDefined(); | |
| expect(result.data).toBeDefined(); | |
| expect(result.source).toBeDefined(); | |
| expect(result.latencyMs).toBeGreaterThanOrEqual(0); | |
| expect(result.cached).toBe(false); | |
| }); | |
| it('should provide agent statistics', async () => { | |
| const stats = await agent.getStats(); | |
| expect(stats).toHaveProperty('totalDecisions'); | |
| expect(stats).toHaveProperty('averageConfidence'); | |
| expect(stats).toHaveProperty('topSources'); | |
| }); | |
| }); | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // COGNITIVE MEMORY INTEGRATION TESTS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| describe('CognitiveMemory - Unified Interface', () => { | |
| let cognitiveMemory: any; | |
| beforeEach(async () => { | |
| const { initCognitiveMemory, getCognitiveMemory } = await import('../mcp/memory/CognitiveMemory.js'); | |
| initCognitiveMemory(db); | |
| cognitiveMemory = getCognitiveMemory(); | |
| }); | |
| it('should initialize correctly', () => { | |
| expect(cognitiveMemory).toBeDefined(); | |
| }); | |
| it('should record successful queries', async () => { | |
| await cognitiveMemory.recordSuccess({ | |
| widgetId: 'cognitive-test', | |
| queryType: 'data.fetch', | |
| queryParams: { id: 123 }, | |
| sourceUsed: 'test-source', | |
| latencyMs: 75, | |
| resultSize: 2048 | |
| }); | |
| // Query should be recorded in pattern memory | |
| const patterns = await cognitiveMemory.patternMemory.getWidgetPatterns('cognitive-test'); | |
| expect(patterns.queryCount).toBeGreaterThan(0); | |
| }); | |
| it('should record failures with recovery info', async () => { | |
| const error = new Error('Test failure'); | |
| await cognitiveMemory.recordFailure({ | |
| sourceName: 'cognitive-failure-test', | |
| error, | |
| queryContext: { operation: 'test' }, | |
| recoveryAction: 'retry', | |
| recoverySuccess: true, | |
| recoveryTimeMs: 100 | |
| }); | |
| const history = await cognitiveMemory.failureMemory.getFailureHistory('cognitive-failure-test'); | |
| expect(history.length).toBeGreaterThan(0); | |
| }); | |
| it('should get source intelligence combining patterns and failures', async () => { | |
| const intel = await cognitiveMemory.getSourceIntelligence('test-source'); | |
| expect(intel).toHaveProperty('averageLatency'); | |
| expect(intel).toHaveProperty('overallSuccessRate'); | |
| expect(intel).toHaveProperty('recentFailures'); | |
| }); | |
| }); | |