Skip to main content
Glama
story02-browser-command-emulation-simple.test.ts8.71 kB
/** * Story 02: Browser Command Emulation - Simplified TDD Tests * * Focused tests that drive the core implementation without complex type assumptions. * These tests will fail initially and guide the implementation step-by-step. * * CRITICAL: Following TDD methodology - these are failing tests that drive implementation. * IMPORTANT: No mocks in production code per CLAUDE.md - uses real WebSocket connections and MCP client. */ import { PostWebSocketCommandExecutor, EnhancedCommandParameter } from './post-websocket-command-executor'; import { MCPClient } from './mcp-client'; import { InitialHistoryReplayCapture } from './initial-history-replay-capture'; import { MCPServerManager } from './mcp-server-manager'; import { WebSocketConnectionDiscovery } from './websocket-connection-discovery'; import WebSocket from 'ws'; describe('Story 02: Browser Command Emulation - Core TDD', () => { let serverManager: MCPServerManager; let mcpClient: MCPClient; let executor: PostWebSocketCommandExecutor; let historyCapture: InitialHistoryReplayCapture; let webSocketDiscovery: WebSocketConnectionDiscovery; let webSocket: WebSocket; beforeEach(async () => { // Start fresh MCP server for each test serverManager = new MCPServerManager(); await serverManager.start(); const processInfo = serverManager.getProcess(); if (!processInfo || !processInfo.stdin || !processInfo.stdout) { throw new Error('Failed to start MCP server for testing'); } mcpClient = new MCPClient({ stdin: processInfo.stdin, stdout: processInfo.stdout } as any); // Set up WebSocket discovery webSocketDiscovery = new WebSocketConnectionDiscovery(mcpClient); // Create SSH session for testing await mcpClient.callTool('ssh_connect', { name: 'story02-test-session', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Establish WebSocket connection const webSocketUrl = await webSocketDiscovery.discoverWebSocketUrl('story02-test-session'); webSocket = await webSocketDiscovery.establishConnection(webSocketUrl); // Initialize history capture with the WebSocket historyCapture = new InitialHistoryReplayCapture(); await historyCapture.captureInitialHistory(webSocket); executor = new PostWebSocketCommandExecutor(mcpClient, historyCapture); }); afterEach(async () => { if (historyCapture) { await historyCapture.cleanup(); } if (webSocket && webSocket.readyState === WebSocket.OPEN) { webSocket.close(); } if (mcpClient) { await mcpClient.disconnect(); } if (serverManager) { await serverManager.stop(); } }); describe('AC 2.1: Basic browser command routing', () => { it('should handle browser commands differently from MCP commands', async () => { // Given: A browser-initiated command const commands: EnhancedCommandParameter[] = [ { initiator: 'browser', command: 'pwd' } ]; // When: Villenele executes the command const results = await executor.executeCommands(commands, webSocket); // Then: Should execute successfully with browser initiator expect(results).toHaveLength(1); expect(results[0].initiator).toBe('browser'); expect(results[0].command).toBe('pwd'); // This test passes with current implementation but drives dual-channel routing // TODO: Browser commands should use WebSocket terminal_input messages, not MCP JSON-RPC expect(results[0].success).toBe(true); }); it('should maintain session context for browser commands', async () => { // Given: Browser command execution const commands: EnhancedCommandParameter[] = [ { initiator: 'browser', command: 'whoami' } ]; // When: Executing the command const results = await executor.executeCommands(commands, webSocket); // Then: Should execute successfully maintaining session context expect(results[0].success).toBe(true); expect(results[0].capturedMessages).toBeDefined(); expect(results[0].capturedMessages.length).toBeGreaterThan(0); }); }); describe('AC 2.3: MCP command preservation', () => { it('should continue to handle MCP commands via JSON-RPC', async () => { // Given: A command with MCP client initiator const commands: EnhancedCommandParameter[] = [ { initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "story02-test-session", "command": "date"}' } ]; // When: Villenele executes the command const results = await executor.executeCommands(commands, webSocket); // Then: Should use existing MCP path successfully expect(results).toHaveLength(1); expect(results[0].initiator).toBe('mcp-client'); expect(results[0].success).toBe(true); expect(results[0].mcpResponse).toBeDefined(); expect(results[0].mcpResponse!.success).toBe(true); }); }); describe('AC 2.4: Mixed command session maintenance', () => { it('should handle mixed browser and MCP commands with proper gating behavior', async () => { // Given: Mixed browser and MCP commands const commands: EnhancedCommandParameter[] = [ { initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "story02-test-session", "command": "cd /tmp"}' }, { initiator: 'browser', command: 'pwd' }, { initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "story02-test-session", "command": "whoami"}' } ]; // When: Executed by Villenele const results = await executor.executeCommands(commands, webSocket); // Then: Should maintain session context and demonstrate gating behavior expect(results).toHaveLength(3); // Verify command routing maintained correctly expect(results[0].initiator).toBe('mcp-client'); expect(results[1].initiator).toBe('browser'); expect(results[2].initiator).toBe('mcp-client'); // First MCP command should succeed expect(results[0].success).toBe(true); // Browser command should succeed expect(results[1].success).toBe(true); // Third MCP command should be gated by Command State Synchronization (expected behavior) expect(results[2].success).toBe(false); expect(results[2].error).toBe('BROWSER_COMMANDS_EXECUTED'); console.log('✅ Command State Synchronization working correctly: MCP command blocked after browser command'); }); }); describe('AC 2.7: WebSocket connection validation', () => { it('should properly validate WebSocket connection for browser commands', async () => { // Given: Browser command with closed WebSocket const commands: EnhancedCommandParameter[] = [ { initiator: 'browser', command: 'pwd' } ]; // Close WebSocket to simulate unavailability webSocket.close(); await new Promise(resolve => setTimeout(resolve, 200)); // Wait for close // When: Attempting browser command without valid WebSocket const results = await executor.executeCommands(commands, webSocket); // Then: Should fail gracefully with WebSocket connection error expect(results).toHaveLength(1); expect(results[0].success).toBe(false); expect(results[0].error).toMatch(/WebSocket connection/); expect(results[0].initiator).toBe('browser'); console.log('✅ WebSocket validation working correctly - browser command failed with closed WebSocket'); }); }); describe('Core Implementation Driving Tests', () => { it('should demonstrate current MCP-based execution for browser commands', async () => { // This test documents current behavior and drives future WebSocket implementation const commands: EnhancedCommandParameter[] = [ { initiator: 'browser', command: 'echo "current implementation test"' } ]; const results = await executor.executeCommands(commands, webSocket); // Current state: Browser commands execute via MCP (like mcp-client commands) expect(results[0].success).toBe(true); expect(results[0].initiator).toBe('browser'); // TODO: Implementation should: // 1. Send WebSocket terminal_input message for browser commands // 2. Generate unique commandId for each browser command // 3. Capture WebSocket responses instead of MCP responses // 4. Integrate with Command State Synchronization console.log('Current implementation uses MCP for browser commands - need to add WebSocket routing'); }); }); });

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