Skip to main content
Glama
ssh-command-echo-unit.test.ts17.2 kB
import { SSHConnectionManager, TerminalOutputEntry } from "../src/ssh-connection-manager"; import { EventEmitter } from "events"; import { SSHConnectionConfig, } from "../src/types"; // Mock SSH2 Client and related classes jest.mock("ssh2", () => ({ Client: jest.fn().mockImplementation(() => ({ connect: jest.fn(), exec: jest.fn(), destroy: jest.fn(), on: jest.fn().mockReturnThis(), })), })); // Mock for Stream (SSH exec stream) class MockStream extends EventEmitter { stderr = new EventEmitter(); destroy() { this.emit('close', 0); } // Simulate stream data and completion simulateCommand(stdout: string, stderr: string = '', exitCode: number = 0) { // Emit stdout data if (stdout) { this.emit('data', Buffer.from(stdout)); } // Emit stderr data if (stderr) { this.stderr.emit('data', Buffer.from(stderr)); } // Emit completion setTimeout(() => { this.emit('close', exitCode); }, 10); } } describe("SSH Command Echo Unit Tests", () => { let connectionManager: SSHConnectionManager; let mockClient: any; let capturedWebSocketOutput: string; let outputListenerCallback: (entry: TerminalOutputEntry) => void; const testConfig: SSHConnectionConfig = { name: "test-session", host: "localhost", username: "jsbattig", password: "test123", }; // Helper function to set up mock SSH client with proper command responses const setupMockCommands = (commandResponses: Record<string, string>) => { mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { const stream = new MockStream(); setTimeout(() => { callback(undefined, stream); // Simulate command output after the stream is provided to the SSH manager setTimeout(() => { const response = commandResponses[command] || `unknown command: ${command}`; stream.simulateCommand(response); }, 20); // Give time for event listeners to be set up }, 10); return mockClient; }); }; beforeEach(() => { jest.clearAllMocks(); capturedWebSocketOutput = ""; connectionManager = new SSHConnectionManager(); // Get the mocked Client constructor const { Client } = require("ssh2"); mockClient = { connect: jest.fn(), exec: jest.fn(), destroy: jest.fn(), on: jest.fn().mockReturnThis(), }; Client.mockImplementation(() => mockClient); // Capture WebSocket output by mocking the terminal output listener outputListenerCallback = (entry: TerminalOutputEntry) => { capturedWebSocketOutput += entry.output; }; }); afterEach(() => { connectionManager.cleanup(); }); describe("Command Echo Generation - Zero History", () => { it("should generate exact WebSocket output for zero history (just initial prompt)", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Set up mock commands setupMockCommands({ 'pwd': '/home/jsbattig', 'whoami': 'jsbattig' }); // Execute command that should generate echo await connectionManager.executeCommand("test-session", "whoami", { source: "user" }); // EXACT assertion - match the entire WebSocket output string const expectedOutput = "[jsbattig@localhost ~]$ whoami\r\njsbattig"; expect(capturedWebSocketOutput).toBe(expectedOutput); }); }); describe("Command Echo Generation - One Command History", () => { it("should generate exact WebSocket output for single command in history", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Mock pwd command for directory discovery const pwdStream = new MockStream(); mockClient.exec .mockImplementationOnce((command: string, callback: (err: Error | undefined, stream: any) => void) => { if (command === 'pwd') { setTimeout(() => callback(undefined, pwdStream), 10); pwdStream.simulateCommand('/home/jsbattig'); return mockClient; } }) .mockImplementationOnce((command: string, callback: (err: Error | undefined, stream: any) => void) => { if (command === 'pwd') { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); stream.simulateCommand('/home/jsbattig'); return mockClient; } }) .mockImplementationOnce((command: string, callback: (err: Error | undefined, stream: any) => void) => { if (command === 'whoami') { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); stream.simulateCommand('jsbattig'); return mockClient; } }); // Execute first command to establish history await connectionManager.executeCommand("test-session", "pwd", { source: "user" }); // Clear captured output for second command test capturedWebSocketOutput = ""; // Execute second command await connectionManager.executeCommand("test-session", "whoami", { source: "user" }); // EXACT assertion - match the entire WebSocket output string const expectedOutput = "[jsbattig@localhost ~]$ whoami\r\njsbattig"; expect(capturedWebSocketOutput).toBe(expectedOutput); }); }); describe("Command Echo Generation - Multiple Commands History", () => { it("should generate exact WebSocket output for multiple commands in history", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Set up mock for multiple commands let callCount = 0; mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { callCount++; const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); if (command === 'pwd') { stream.simulateCommand('/home/jsbattig'); } else if (command === 'whoami') { stream.simulateCommand('jsbattig'); } else if (command === 'date') { stream.simulateCommand('Wed Jan 15 10:30:45 PST 2025'); } return mockClient; }); // Execute first two commands to build history await connectionManager.executeCommand("test-session", "pwd", { source: "user" }); await connectionManager.executeCommand("test-session", "whoami", { source: "user" }); // Clear captured output for third command test capturedWebSocketOutput = ""; // Execute third command await connectionManager.executeCommand("test-session", "date", { source: "user" }); // EXACT assertion - match the entire WebSocket output string const expectedOutput = "[jsbattig@localhost ~]$ date\r\nWed Jan 15 10:30:45 PST 2025"; expect(capturedWebSocketOutput).toBe(expectedOutput); }); }); describe("Command Echo Generation - Multi-column Output", () => { it("should generate exact WebSocket output for ls -la multi-column output", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Mock commands mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); if (command === 'pwd') { stream.simulateCommand('/home/jsbattig'); } else if (command === 'ls -la') { const lsOutput = `total 32 drwxr-xr-x 4 jsbattig jsbattig 4096 Jan 15 10:30 . drwxr-xr-x 3 root root 4096 Jan 14 09:15 .. -rw-r--r-- 1 jsbattig jsbattig 220 Jan 14 09:15 .bash_logout -rw-r--r-- 1 jsbattig jsbattig 3771 Jan 14 09:15 .bashrc drwx------ 2 jsbattig jsbattig 4096 Jan 15 10:30 .ssh`; stream.simulateCommand(lsOutput); } return mockClient; }); // Execute pwd to establish directory cache await connectionManager.executeCommand("test-session", "pwd", { source: "system" }); // Clear captured output capturedWebSocketOutput = ""; // Execute ls -la command await connectionManager.executeCommand("test-session", "ls -la", { source: "user" }); // EXACT assertion - match the entire WebSocket output string with multi-column formatting const expectedOutput = `[jsbattig@localhost ~]$ ls -la\r\ntotal 32 drwxr-xr-x 4 jsbattig jsbattig 4096 Jan 15 10:30 . drwxr-xr-x 3 root root 4096 Jan 14 09:15 .. -rw-r--r-- 1 jsbattig jsbattig 220 Jan 14 09:15 .bash_logout -rw-r--r-- 1 jsbattig jsbattig 3771 Jan 14 09:15 .bashrc drwx------ 2 jsbattig jsbattig 4096 Jan 15 10:30 .ssh`; expect(capturedWebSocketOutput).toBe(expectedOutput); }); }); describe("CRLF Formatting - xterm Compatibility", () => { it("should use exact CRLF formatting for xterm.js compatibility", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Mock commands mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); if (command === 'pwd') { stream.simulateCommand('/home/jsbattig'); } else if (command === 'echo test') { stream.simulateCommand('test'); } return mockClient; }); // Execute pwd to establish directory cache await connectionManager.executeCommand("test-session", "pwd", { source: "system" }); // Clear captured output capturedWebSocketOutput = ""; // Execute echo command await connectionManager.executeCommand("test-session", "echo test", { source: "claude" }); // EXACT assertion - verify CRLF line endings (\\r\\n) const expectedOutput = "[jsbattig@localhost ~]$ echo test\r\ntest"; expect(capturedWebSocketOutput).toBe(expectedOutput); // Verify it contains proper CRLF sequences expect(capturedWebSocketOutput).toContain('\r\n'); expect(capturedWebSocketOutput.split('\r\n')).toHaveLength(2); }); }); describe("Source-based Command Echo Behavior", () => { it("should generate command echo for user source commands", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Mock commands mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); if (command === 'pwd') { stream.simulateCommand('/home/jsbattig'); } else if (command === 'id') { stream.simulateCommand('uid=1000(jsbattig) gid=1000(jsbattig) groups=1000(jsbattig)'); } return mockClient; }); // Execute pwd to establish directory cache await connectionManager.executeCommand("test-session", "pwd", { source: "system" }); // Clear captured output capturedWebSocketOutput = ""; // Execute command with user source await connectionManager.executeCommand("test-session", "id", { source: "user" }); // EXACT assertion - should include command echo for user source const expectedOutput = "[jsbattig@localhost ~]$ id\r\nuid=1000(jsbattig) gid=1000(jsbattig) groups=1000(jsbattig)"; expect(capturedWebSocketOutput).toBe(expectedOutput); }); it("should generate command echo for claude source commands", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Mock commands mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); if (command === 'pwd') { stream.simulateCommand('/home/jsbattig'); } else if (command === 'hostname') { stream.simulateCommand('localhost'); } return mockClient; }); // Execute pwd to establish directory cache await connectionManager.executeCommand("test-session", "pwd", { source: "system" }); // Clear captured output capturedWebSocketOutput = ""; // Execute command with claude source await connectionManager.executeCommand("test-session", "hostname", { source: "claude" }); // EXACT assertion - should include command echo for claude source const expectedOutput = "[jsbattig@localhost ~]$ hostname\r\nlocalhost"; expect(capturedWebSocketOutput).toBe(expectedOutput); }); it("should NOT generate command echo for system source commands", async () => { // Mock successful SSH connection mockClient.on.mockImplementation((event: string, callback: (...args: any[]) => void) => { if (event === 'ready') { setTimeout(() => callback(), 10); } return mockClient; }); // Create connection await connectionManager.createConnection(testConfig); // Add WebSocket listener to capture output connectionManager.addTerminalOutputListener("test-session", outputListenerCallback); // Mock commands mockClient.exec.mockImplementation((command: string, callback: (err: Error | undefined, stream: any) => void) => { const stream = new MockStream(); setTimeout(() => callback(undefined, stream), 10); if (command === 'uptime') { stream.simulateCommand('10:30:45 up 1 day, 2:15, 1 user, load average: 0.15, 0.10, 0.08'); } return mockClient; }); // Execute command with system source await connectionManager.executeCommand("test-session", "uptime", { source: "system" }); // EXACT assertion - should NOT include command echo for system source const expectedOutput = "10:30:45 up 1 day, 2:15, 1 user, load average: 0.15, 0.10, 0.08"; expect(capturedWebSocketOutput).toBe(expectedOutput); }); }); });

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