Skip to main content
Glama
robust-error-diagnostics-integration.test.ts18.9 kB
/** * Story 8: Robust Error Handling and Diagnostics - Integration Tests * * Integration tests for RobustErrorDiagnostics with real framework components. * These tests will fail initially and force implementation of real error handling, * server log capture, WebSocket state monitoring, and resource cleanup. * * Test Coverage: * 1. Real error scenarios with actual framework components * 2. Server log capture during errors * 3. WebSocket state monitoring during connection failures * 4. Resource cleanup verification during error conditions * 5. Command sequence tracking through real workflow failures * 6. Integration with all Stories 1-7 components * * CRITICAL: Integration tests with NO MOCKS. * Uses real framework components to test error diagnostics. */ import { RobustErrorDiagnostics } from './robust-error-diagnostics'; import { MCPServerManager } from './mcp-server-manager'; import { MCPClient } from './mcp-client'; import { PreWebSocketCommandExecutor } from './pre-websocket-command-executor'; import { WebSocketConnectionDiscovery } from './websocket-connection-discovery'; import { InitialHistoryReplayCapture } from './initial-history-replay-capture'; import { PostWebSocketCommandExecutor } from './post-websocket-command-executor'; import path from 'path'; describe('RobustErrorDiagnostics Integration Tests', () => { let diagnostics: RobustErrorDiagnostics; let serverManager: MCPServerManager; let mcpClient: MCPClient; let preExecutor: PreWebSocketCommandExecutor; let discovery: WebSocketConnectionDiscovery; let historyCapture: InitialHistoryReplayCapture; let postExecutor: PostWebSocketCommandExecutor; const testSessionName = 'diagnostic-integration-test'; beforeEach(async () => { // Create real framework components const serverPath = path.join(__dirname, '../../..', 'dist/src/mcp-ssh-server.js'); serverManager = new MCPServerManager({ serverPath }); // Start server to get process for MCP client await serverManager.start(); const rawProcess = serverManager.getRawProcess(); if (!rawProcess) { throw new Error('Failed to get raw process from server manager'); } mcpClient = new MCPClient(rawProcess); preExecutor = new PreWebSocketCommandExecutor(mcpClient); discovery = new WebSocketConnectionDiscovery(mcpClient); historyCapture = new InitialHistoryReplayCapture(); postExecutor = new PostWebSocketCommandExecutor(); // Create diagnostics with real configuration diagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName, enableServerLogCapture: true, enableWebSocketStateMonitoring: true, maxServerLogLines: 50, workflowTimeout: 15000, historyReplayTimeout: 5000, commandTimeout: 10000 }); // Inject real components diagnostics.setServerManager(serverManager); diagnostics.setMcpClient(mcpClient); diagnostics.setPreWebSocketExecutor(preExecutor); diagnostics.setConnectionDiscovery(discovery); diagnostics.setHistoryCapture(historyCapture); diagnostics.setPostWebSocketExecutor(postExecutor); }); afterEach(async () => { // Comprehensive cleanup if (diagnostics) { await diagnostics.cleanup(); } if (serverManager && serverManager.isRunning()) { await serverManager.stop(); } if (mcpClient) { await mcpClient.disconnect(); } if (preExecutor) { await preExecutor.cleanup(); } if (historyCapture) { await historyCapture.cleanup(); } if (postExecutor) { await postExecutor.cleanup(); } }); describe('Real Error Scenario Handling', () => { test('should capture diagnostic reports during server startup failure', async () => { // Create diagnostics with invalid server path to force startup failure const invalidServerPath = '/invalid/path/to/server'; const failingServerManager = new MCPServerManager({ serverPath: invalidServerPath }); // Create a minimal client setup for this test (without starting server) const mockProcess = { stdin: { write: jest.fn() }, stdout: { on: jest.fn() }, stderr: { on: jest.fn() }, kill: jest.fn() } as any; const failingClient = new MCPClient(mockProcess); const failingPreExecutor = new PreWebSocketCommandExecutor(failingClient); const failingDiscovery = new WebSocketConnectionDiscovery(failingClient); const failingHistory = new InitialHistoryReplayCapture(); const failingPostExecutor = new PostWebSocketCommandExecutor(); const failingDiagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName + '-server-fail', enableServerLogCapture: true, enableWebSocketStateMonitoring: true }); failingDiagnostics.setServerManager(failingServerManager); failingDiagnostics.setMcpClient(failingClient); failingDiagnostics.setPreWebSocketExecutor(failingPreExecutor); failingDiagnostics.setConnectionDiscovery(failingDiscovery); failingDiagnostics.setHistoryCapture(failingHistory); failingDiagnostics.setPostWebSocketExecutor(failingPostExecutor); // Execute workflow expecting failure const result = await failingDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); // Should fail but with diagnostic reports expect(result.success).toBe(false); expect(result.diagnosticReports).toBeDefined(); expect(result.diagnosticReports!.length).toBeGreaterThan(0); // Check diagnostic report content const report = result.diagnosticReports![0]; expect(report.timestamp).toBeDefined(); expect(report.phase).toMatch(/^(server-startup|configuration|websocket-connection|pre-websocket|history-replay|post-websocket)$/); expect(report.errorType).toMatch(/^(framework|application)$/); expect(report.message).toBeDefined(); expect(report.context.commandSequence).toBeDefined(); expect(report.context.resourceStates).toBeDefined(); expect(report.debuggingTips.length).toBeGreaterThan(0); // Cleanup components await failingDiagnostics.cleanup(); await failingPreExecutor.cleanup(); await failingHistory.cleanup(); await failingPostExecutor.cleanup(); }); test('should track resource states through workflow phases', async () => { // This will likely fail initially due to missing component implementations try { await diagnostics.executeComprehensiveWorkflowWithDiagnostics(); // If successful, verify resource state tracking const resourceStates = diagnostics.getResourceStates(); expect(resourceStates.size).toBeGreaterThan(0); // Check if resource states were updated during workflow for (const [name, state] of resourceStates) { expect(state.name).toBe(name); expect(state.lastUpdated).toBeDefined(); expect(state.status).toMatch(/^(initialized|running|stopped|error|unknown)$/); } } catch (error) { // If it fails, ensure diagnostic reports were created const reports = diagnostics.getDiagnosticReports(); expect(reports.length).toBeGreaterThan(0); // Verify resource states are captured in diagnostic context const report = reports[0]; expect(report.context.resourceStates).toBeDefined(); expect(Object.keys(report.context.resourceStates).length).toBeGreaterThan(0); } }); }); describe('Server Log Capture', () => { test('should capture real server logs during error scenarios', async () => { // Start server first to generate logs try { await serverManager.start(); // Let it run briefly to generate logs await new Promise(resolve => setTimeout(resolve, 1000)); } catch { // Expected if server fails to start } // Now execute workflow which may fail but should capture logs const result = await diagnostics.executeComprehensiveWorkflowWithDiagnostics(); if (result.diagnosticReports && result.diagnosticReports.length > 0) { const report = result.diagnosticReports[0]; // Server logs should be captured expect(report.context.serverLogs).toBeDefined(); expect(Array.isArray(report.context.serverLogs)).toBe(true); // Should have real logs, not just placeholder if (report.context.serverLogs.length > 0) { expect(report.context.serverLogs[0]).not.toBe('Server log capture not yet implemented'); } } }); test('should respect maxServerLogLines configuration', async () => { const limitedLogsDiagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName + '-limited-logs', enableServerLogCapture: true, maxServerLogLines: 5 }); // Set up components limitedLogsDiagnostics.setServerManager(serverManager); limitedLogsDiagnostics.setMcpClient(mcpClient); limitedLogsDiagnostics.setPreWebSocketExecutor(preExecutor); limitedLogsDiagnostics.setConnectionDiscovery(discovery); limitedLogsDiagnostics.setHistoryCapture(historyCapture); limitedLogsDiagnostics.setPostWebSocketExecutor(postExecutor); try { await limitedLogsDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); } catch { // Expected to fail } const reports = limitedLogsDiagnostics.getDiagnosticReports(); if (reports.length > 0) { const report = reports[0]; // Should respect the 5 line limit expect(report.context.serverLogs.length).toBeLessThanOrEqual(5); } await limitedLogsDiagnostics.cleanup(); }); }); describe('WebSocket State Monitoring', () => { test('should capture WebSocket connection state during failures', async () => { // Execute workflow which will likely fail at WebSocket connection phase const result = await diagnostics.executeComprehensiveWorkflowWithDiagnostics(); if (result.diagnosticReports && result.diagnosticReports.length > 0) { const report = result.diagnosticReports[0]; // WebSocket state should be captured when monitoring is enabled expect(report.context.webSocketState).toBeDefined(); // Should have real state information, not just placeholder expect(report.context.webSocketState).not.toBe('WebSocket state capture not yet implemented'); } }); test('should skip WebSocket monitoring when disabled', async () => { const noMonitoringDiagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName + '-no-monitoring', enableWebSocketStateMonitoring: false }); // Set up components noMonitoringDiagnostics.setServerManager(serverManager); noMonitoringDiagnostics.setMcpClient(mcpClient); noMonitoringDiagnostics.setPreWebSocketExecutor(preExecutor); noMonitoringDiagnostics.setConnectionDiscovery(discovery); noMonitoringDiagnostics.setHistoryCapture(historyCapture); noMonitoringDiagnostics.setPostWebSocketExecutor(postExecutor); try { await noMonitoringDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); } catch { // Expected to fail } const reports = noMonitoringDiagnostics.getDiagnosticReports(); if (reports.length > 0) { const report = reports[0]; expect(report.context.webSocketState).toBeUndefined(); } await noMonitoringDiagnostics.cleanup(); }); }); describe('Command Sequence Tracking', () => { test('should track command sequence through workflow execution', async () => { // Configure with pre and post WebSocket commands const commandTrackingDiagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName + '-command-tracking', preWebSocketCommands: [ { tool: 'ssh_create_session', args: { sessionName: testSessionName + '-tracking' } } ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ls'}, {initiator: 'mcp-client', command: 'pwd'} ] }); // Set up components commandTrackingDiagnostics.setServerManager(serverManager); commandTrackingDiagnostics.setMcpClient(mcpClient); commandTrackingDiagnostics.setPreWebSocketExecutor(preExecutor); commandTrackingDiagnostics.setConnectionDiscovery(discovery); commandTrackingDiagnostics.setHistoryCapture(historyCapture); commandTrackingDiagnostics.setPostWebSocketExecutor(postExecutor); try { await commandTrackingDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); } catch { // Expected to fail } const reports = commandTrackingDiagnostics.getDiagnosticReports(); if (reports.length > 0) { const report = reports[0]; // Command sequence should be tracked expect(report.context.commandSequence).toBeDefined(); expect(Array.isArray(report.context.commandSequence)).toBe(true); // Should contain the commands we configured // This will fail until command tracking is properly implemented expect(report.context.commandSequence.length).toBeGreaterThan(0); } await commandTrackingDiagnostics.cleanup(); }); }); describe('Error Classification Integration', () => { test('should classify real framework errors correctly', async () => { // Force a framework error by using invalid server configuration const frameworkErrorDiagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName + '-framework-error', workflowTimeout: 100 // Very short timeout to force framework timeout error }); frameworkErrorDiagnostics.setServerManager(serverManager); frameworkErrorDiagnostics.setMcpClient(mcpClient); frameworkErrorDiagnostics.setPreWebSocketExecutor(preExecutor); frameworkErrorDiagnostics.setConnectionDiscovery(discovery); frameworkErrorDiagnostics.setHistoryCapture(historyCapture); frameworkErrorDiagnostics.setPostWebSocketExecutor(postExecutor); const result = await frameworkErrorDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); expect(result.success).toBe(false); expect(result.diagnosticReports!.length).toBeGreaterThan(0); const report = result.diagnosticReports![0]; expect(report.errorType).toBe('framework'); expect(report.message).toContain('timeout'); await frameworkErrorDiagnostics.cleanup(); }); test('should classify application configuration errors correctly', async () => { // Force an application error by providing invalid configuration const appErrorDiagnostics = new RobustErrorDiagnostics({ sessionName: '', // Invalid empty session name preWebSocketCommands: [ { tool: 'invalid_command', args: {} } // Invalid command ] }); appErrorDiagnostics.setServerManager(serverManager); appErrorDiagnostics.setMcpClient(mcpClient); appErrorDiagnostics.setPreWebSocketExecutor(preExecutor); appErrorDiagnostics.setConnectionDiscovery(discovery); appErrorDiagnostics.setHistoryCapture(historyCapture); appErrorDiagnostics.setPostWebSocketExecutor(postExecutor); const result = await appErrorDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); expect(result.success).toBe(false); expect(result.diagnosticReports!.length).toBeGreaterThan(0); // Should classify as application error const report = result.diagnosticReports![0]; expect(report.errorType).toBe('application'); await appErrorDiagnostics.cleanup(); }); }); describe('Resource Cleanup Verification', () => { test('should guarantee resource cleanup even during critical failures', async () => { // Create a scenario that will fail at various phases const cleanupTestDiagnostics = new RobustErrorDiagnostics({ sessionName: testSessionName + '-cleanup-test', workflowTimeout: 2000 // Short timeout to force failure }); cleanupTestDiagnostics.setServerManager(serverManager); cleanupTestDiagnostics.setMcpClient(mcpClient); cleanupTestDiagnostics.setPreWebSocketExecutor(preExecutor); cleanupTestDiagnostics.setConnectionDiscovery(discovery); cleanupTestDiagnostics.setHistoryCapture(historyCapture); cleanupTestDiagnostics.setPostWebSocketExecutor(postExecutor); const result = await cleanupTestDiagnostics.executeComprehensiveWorkflowWithDiagnostics(); // Should indicate cleanup success even if workflow failed expect(result.resourceCleanupSuccess).toBe(true); // Additional cleanup verification const additionalCleanupSuccess = await cleanupTestDiagnostics.cleanup(); expect(additionalCleanupSuccess).toBe(true); // Resource states should reflect cleanup const finalResourceStates = cleanupTestDiagnostics.getResourceStates(); for (const [, state] of finalResourceStates) { // After cleanup, resources should be stopped or have cleanup attempted expect(state.details.cleanupAttempted).toBe(true); } }); }); describe('Debugging Information Quality', () => { test('should provide actionable debugging tips for each error phase', async () => { const result = await diagnostics.executeComprehensiveWorkflowWithDiagnostics(); if (result.diagnosticReports && result.diagnosticReports.length > 0) { const report = result.diagnosticReports[0]; // Should have debugging tips expect(report.debuggingTips).toBeDefined(); expect(Array.isArray(report.debuggingTips)).toBe(true); expect(report.debuggingTips.length).toBeGreaterThan(0); // Tips should be actionable (contain specific guidance) const tipText = report.debuggingTips.join(' ').toLowerCase(); expect(tipText).toMatch(/verify|check|ensure|configure/); } }); test('should include stack trace information for debugging', async () => { const result = await diagnostics.executeComprehensiveWorkflowWithDiagnostics(); if (result.diagnosticReports && result.diagnosticReports.length > 0) { const report = result.diagnosticReports[0]; // Should include stack trace for errors if (report.stackTrace) { expect(report.stackTrace).toBeDefined(); expect(typeof report.stackTrace).toBe('string'); expect(report.stackTrace.length).toBeGreaterThan(0); } } }); }); });

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/LightspeedDMS/ssh-mcp'

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