Skip to main content
Glama
comprehensive-response-collector.test.ts16 kB
/** * Story 6: Comprehensive Response Collection and Output - Unit Tests * * Unit tests for ComprehensiveResponseCollector class that orchestrates * the complete terminal history testing framework workflow. * * This tests the orchestration of Stories 1-5 components in a complete workflow. * * CRITICAL: Following strict TDD red-green-refactor cycle. */ import { ComprehensiveResponseCollector, ComprehensiveResponseCollectorConfig } from './comprehensive-response-collector'; 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 WebSocket from 'ws'; describe('ComprehensiveResponseCollector', () => { let collector: ComprehensiveResponseCollector; let mockServerManager: jest.Mocked<MCPServerManager>; let mockMcpClient: jest.Mocked<MCPClient>; let mockPreWebSocketExecutor: jest.Mocked<PreWebSocketCommandExecutor>; let mockConnectionDiscovery: jest.Mocked<WebSocketConnectionDiscovery>; let mockHistoryCapture: jest.Mocked<InitialHistoryReplayCapture>; let mockPostWebSocketExecutor: jest.Mocked<PostWebSocketCommandExecutor>; let mockWebSocket: jest.Mocked<WebSocket>; beforeEach(() => { // Create mocks for unit testing (only for testing the orchestration logic) mockServerManager = { start: jest.fn(), stop: jest.fn(), isRunning: jest.fn(), getProcess: jest.fn() } as any; mockMcpClient = { isConnected: jest.fn(), callTool: jest.fn(), disconnect: jest.fn() } as any; mockPreWebSocketExecutor = { executeCommands: jest.fn(), cleanup: jest.fn(), isReady: jest.fn() } as any; mockConnectionDiscovery = { discoverWebSocketUrl: jest.fn(), establishConnection: jest.fn(), validateConnection: jest.fn() } as any; mockHistoryCapture = { captureInitialHistory: jest.fn(), waitForHistoryReplayComplete: jest.fn(), getHistoryMessages: jest.fn(), getRealTimeMessages: jest.fn(), cleanup: jest.fn() } as any; mockPostWebSocketExecutor = { executeCommands: jest.fn(), cleanup: jest.fn() } as any; mockWebSocket = { readyState: WebSocket.OPEN, close: jest.fn(), terminate: jest.fn(), removeAllListeners: jest.fn() } as any; collector = new ComprehensiveResponseCollector(); }); afterEach(async () => { await collector.cleanup(); }); describe('constructor', () => { it('should create instance with default configuration', () => { const collector = new ComprehensiveResponseCollector(); expect(collector).toBeInstanceOf(ComprehensiveResponseCollector); }); it('should create instance with custom configuration', () => { const config: ComprehensiveResponseCollectorConfig = { workflowTimeout: 15000, sessionName: 'custom-session', preWebSocketCommands: [{ tool: 'ssh_exec', args: { command: 'ls' } }], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec ls -la'} ], historyReplayTimeout: 5000, commandTimeout: 25000 }; const collector = new ComprehensiveResponseCollector(config); expect(collector).toBeInstanceOf(ComprehensiveResponseCollector); expect(collector.getConfig()).toEqual(expect.objectContaining({ workflowTimeout: 15000, sessionName: 'custom-session' })); }); it('should validate configuration parameters', () => { expect(() => { new ComprehensiveResponseCollector({ workflowTimeout: -1000 }); }).toThrow('workflowTimeout must be positive'); expect(() => { new ComprehensiveResponseCollector({ historyReplayTimeout: 0 }); }).toThrow('historyReplayTimeout must be positive'); expect(() => { new ComprehensiveResponseCollector({ sessionName: '' }); }).toThrow('sessionName cannot be empty'); }); }); describe('executeComprehensiveWorkflow', () => { it('should fail when called before components are provided', async () => { await expect(collector.executeComprehensiveWorkflow()).rejects.toThrow('Framework components not initialized'); }); it('should execute complete workflow orchestration', async () => { // Setup mocks mockServerManager.isRunning.mockReturnValue(false); mockServerManager.start.mockResolvedValue(); mockMcpClient.isConnected.mockReturnValue(true); mockPreWebSocketExecutor.executeCommands.mockResolvedValue([]); mockConnectionDiscovery.discoverWebSocketUrl.mockResolvedValue('ws://localhost:8083/ws/session/test-session'); mockConnectionDiscovery.establishConnection.mockResolvedValue(mockWebSocket); mockConnectionDiscovery.validateConnection.mockReturnValue(true); mockHistoryCapture.captureInitialHistory.mockResolvedValue(); mockHistoryCapture.waitForHistoryReplayComplete.mockResolvedValue(); mockHistoryCapture.getHistoryMessages.mockReturnValue([ { timestamp: 1000, data: 'History message\r\n', type: 'history_replay', isHistoryReplay: true, sequenceNumber: 1 } ]); mockHistoryCapture.getRealTimeMessages.mockReturnValue([ { timestamp: 2000, data: 'Real-time message\r\n', type: 'websocket_received', isHistoryReplay: false, sequenceNumber: 2 } ]); mockPostWebSocketExecutor.executeCommands.mockResolvedValue([]); // Set components collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); const result = await collector.executeComprehensiveWorkflow(); expect(result.success).toBe(true); expect(result.concatenatedResponses).toBe('History message\r\nReal-time message\r\n'); expect(result.phaseBreakdown).toBeDefined(); expect(result.totalExecutionTime).toBeGreaterThan(0); }); it('should handle workflow timeout gracefully', async () => { const timeoutConfig: ComprehensiveResponseCollectorConfig = { workflowTimeout: 100 // Very short timeout }; collector = new ComprehensiveResponseCollector(timeoutConfig); // Setup mocks that delay longer than timeout mockServerManager.start.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 200)) ); mockMcpClient.isConnected.mockReturnValue(true); collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); const result = await collector.executeComprehensiveWorkflow(); expect(result.success).toBe(false); expect(result.error).toContain('Workflow timeout'); expect(result.totalExecutionTime).toBeGreaterThanOrEqual(100); }); it('should preserve exact CRLF line endings in concatenated responses', async () => { // Setup mocks with specific CRLF formatting mockServerManager.isRunning.mockReturnValue(false); mockServerManager.start.mockResolvedValue(); mockMcpClient.isConnected.mockReturnValue(true); mockPreWebSocketExecutor.executeCommands.mockResolvedValue([]); mockConnectionDiscovery.discoverWebSocketUrl.mockResolvedValue('ws://localhost:8083/ws/session/test-session'); mockConnectionDiscovery.establishConnection.mockResolvedValue(mockWebSocket); mockConnectionDiscovery.validateConnection.mockReturnValue(true); mockHistoryCapture.captureInitialHistory.mockResolvedValue(); mockHistoryCapture.waitForHistoryReplayComplete.mockResolvedValue(); mockHistoryCapture.getHistoryMessages.mockReturnValue([ { timestamp: 1000, data: 'Line 1\r\n', type: 'history_replay', isHistoryReplay: true, sequenceNumber: 1 }, { timestamp: 1001, data: 'Line 2\r\n', type: 'history_replay', isHistoryReplay: true, sequenceNumber: 2 } ]); mockHistoryCapture.getRealTimeMessages.mockReturnValue([ { timestamp: 2000, data: 'Real-time 1\r\n', type: 'websocket_received', isHistoryReplay: false, sequenceNumber: 3 }, { timestamp: 2001, data: 'Real-time 2\r\n', type: 'websocket_received', isHistoryReplay: false, sequenceNumber: 4 } ]); mockPostWebSocketExecutor.executeCommands.mockResolvedValue([]); collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); const result = await collector.executeComprehensiveWorkflow(); expect(result.concatenatedResponses).toBe('Line 1\r\nLine 2\r\nReal-time 1\r\nReal-time 2\r\n'); expect(result.concatenatedResponses.includes('\r\n')).toBe(true); expect(result.concatenatedResponses.includes('\n\r')).toBe(false); // Ensure no line ending corruption }); it('should provide clear separation between message phases', async () => { // Setup mocks mockServerManager.isRunning.mockReturnValue(false); mockServerManager.start.mockResolvedValue(); mockMcpClient.isConnected.mockReturnValue(true); mockPreWebSocketExecutor.executeCommands.mockResolvedValue([]); mockConnectionDiscovery.discoverWebSocketUrl.mockResolvedValue('ws://localhost:8083/ws/session/test-session'); mockConnectionDiscovery.establishConnection.mockResolvedValue(mockWebSocket); mockConnectionDiscovery.validateConnection.mockReturnValue(true); mockHistoryCapture.captureInitialHistory.mockResolvedValue(); mockHistoryCapture.waitForHistoryReplayComplete.mockResolvedValue(); mockHistoryCapture.getHistoryMessages.mockReturnValue([ { timestamp: 1000, data: 'History\r\n', type: 'history_replay', isHistoryReplay: true, sequenceNumber: 1 } ]); mockHistoryCapture.getRealTimeMessages.mockReturnValue([ { timestamp: 2000, data: 'Real-time\r\n', type: 'websocket_received', isHistoryReplay: false, sequenceNumber: 2 } ]); mockPostWebSocketExecutor.executeCommands.mockResolvedValue([]); collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); const result = await collector.executeComprehensiveWorkflow(); expect(result.phaseBreakdown).toBeDefined(); expect(result.phaseBreakdown!.historyReplayMessages).toEqual(['History\r\n']); expect(result.phaseBreakdown!.realTimeMessages).toEqual(['Real-time\r\n']); expect(result.phaseBreakdown!.historyMessageCount).toBe(1); expect(result.phaseBreakdown!.realTimeMessageCount).toBe(1); }); }); describe('resource cleanup', () => { it('should cleanup all resources when explicitly called after successful workflow', async () => { // Setup mocks for successful workflow mockServerManager.isRunning.mockReturnValue(false); mockServerManager.start.mockResolvedValue(); mockServerManager.stop.mockResolvedValue(); mockMcpClient.isConnected.mockReturnValue(true); mockMcpClient.disconnect.mockResolvedValue(); mockPreWebSocketExecutor.executeCommands.mockResolvedValue([]); mockPreWebSocketExecutor.cleanup.mockResolvedValue(); mockConnectionDiscovery.discoverWebSocketUrl.mockResolvedValue('ws://localhost:8083/ws/session/test-session'); mockConnectionDiscovery.establishConnection.mockResolvedValue(mockWebSocket); mockConnectionDiscovery.validateConnection.mockReturnValue(true); mockHistoryCapture.captureInitialHistory.mockResolvedValue(); mockHistoryCapture.waitForHistoryReplayComplete.mockResolvedValue(); mockHistoryCapture.getHistoryMessages.mockReturnValue([]); mockHistoryCapture.getRealTimeMessages.mockReturnValue([]); mockHistoryCapture.cleanup.mockResolvedValue(); mockPostWebSocketExecutor.executeCommands.mockResolvedValue([]); mockPostWebSocketExecutor.cleanup.mockResolvedValue(); collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); const result = await collector.executeComprehensiveWorkflow(); expect(result.success).toBe(true); // Reset mocks to verify explicit cleanup jest.clearAllMocks(); // Call explicit cleanup await collector.cleanup(); // Verify cleanup was called expect(mockServerManager.stop).toHaveBeenCalled(); expect(mockMcpClient.disconnect).toHaveBeenCalled(); expect(mockHistoryCapture.cleanup).toHaveBeenCalled(); expect(mockPreWebSocketExecutor.cleanup).toHaveBeenCalled(); expect(mockPostWebSocketExecutor.cleanup).toHaveBeenCalled(); }); it('should cleanup all resources when workflow fails', async () => { // Setup mocks for failing workflow mockServerManager.start.mockRejectedValue(new Error('Server failed to start')); mockServerManager.stop.mockResolvedValue(); mockMcpClient.disconnect.mockResolvedValue(); mockPreWebSocketExecutor.cleanup.mockResolvedValue(); mockHistoryCapture.cleanup.mockResolvedValue(); mockPostWebSocketExecutor.cleanup.mockResolvedValue(); collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); const result = await collector.executeComprehensiveWorkflow(); expect(result.success).toBe(false); expect(result.error).toContain('Server failed to start'); }); }); describe('component management', () => { it('should allow setting framework components', () => { collector.setServerManager(mockServerManager); collector.setMcpClient(mockMcpClient); collector.setPreWebSocketExecutor(mockPreWebSocketExecutor); collector.setConnectionDiscovery(mockConnectionDiscovery); collector.setHistoryCapture(mockHistoryCapture); collector.setPostWebSocketExecutor(mockPostWebSocketExecutor); expect(collector.areComponentsInitialized()).toBe(true); }); it('should report components as not initialized when missing', () => { expect(collector.areComponentsInitialized()).toBe(false); }); it('should provide configuration access', () => { const config = collector.getConfig(); expect(config).toBeDefined(); expect(config.workflowTimeout).toBeDefined(); expect(config.sessionName).toBeDefined(); }); }); });

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