Skip to main content
Glama
story5-terminal-interaction-unit.test.ts7.94 kB
import { SSHConnectionManager } from "../src/ssh-connection-manager"; import { SSHConnectionConfig } from "../src/types"; /* eslint-disable @typescript-eslint/no-explicit-any */ // Mock the SSH2 client for unit testing jest.mock("ssh2", () => { const mockChannel = { write: jest.fn(), setWindow: jest.fn(), on: jest.fn(), removeListener: jest.fn(), end: jest.fn(), }; const mockClient = { connect: jest.fn(), shell: jest.fn((callback) => { // Simulate successful shell creation setTimeout(() => callback(null, mockChannel), 10); }), destroy: jest.fn(), on: jest.fn(), }; return { Client: jest.fn(() => mockClient), ClientChannel: jest.fn(), }; }); describe("Story 5: Terminal Interaction - Unit Tests", () => { let sshManager: SSHConnectionManager; let mockClient: any; let mockChannel: any; beforeEach(() => { jest.clearAllMocks(); sshManager = new SSHConnectionManager(8080); // Get references to mocked objects const ClientMock = require("ssh2").Client; mockClient = new ClientMock(); mockChannel = { write: jest.fn(), setWindow: jest.fn(), on: jest.fn(), removeListener: jest.fn(), end: jest.fn(), }; // Configure mock shell method mockClient.shell.mockImplementation((callback: any) => { setTimeout(() => callback(null, mockChannel), 10); }); }); afterEach(() => { sshManager.cleanup(); }); describe("sendTerminalInput method", () => { it("should throw error for non-existent session", () => { expect(() => { sshManager.sendTerminalInput("nonexistent", "test command"); }).toThrow("Session 'nonexistent' not found"); }); it("should send input to shell channel when session exists", async () => { // Create a mock session manually const config: SSHConnectionConfig = { name: "test-session", host: "localhost", username: "test", password: "test", }; // Mock the client ready event mockClient.on.mockImplementation((event: string, callback: any) => { if (event === "ready") { setTimeout(callback, 10); } }); // Create connection const connectionPromise = sshManager.createConnection(config); // Trigger ready event const readyCallback = mockClient.on.mock.calls.find( (call: any) => call[0] === "ready", )[1]; readyCallback(); await connectionPromise; // Wait for shell to be ready await new Promise((resolve) => setTimeout(resolve, 20)); // Mock that shell is ready const sessionData = (sshManager as any).connections.get("test-session"); if (sessionData) { sessionData.isShellReady = true; sessionData.shellChannel = mockChannel; } // Test sending input sshManager.sendTerminalInput("test-session", "echo hello\n"); expect(mockChannel.write).toHaveBeenCalledWith("echo hello\n"); }); }); describe("sendTerminalSignal method", () => { beforeEach(async () => { // Setup a test session const config: SSHConnectionConfig = { name: "signal-test", host: "localhost", username: "test", password: "test", }; mockClient.on.mockImplementation((event: string, callback: any) => { if (event === "ready") { setTimeout(callback, 10); } }); const connectionPromise = sshManager.createConnection(config); const readyCallback = mockClient.on.mock.calls.find( (call: any) => call[0] === "ready", )[1]; readyCallback(); await connectionPromise; await new Promise((resolve) => setTimeout(resolve, 20)); // Mock ready state const sessionData = (sshManager as any).connections.get("signal-test"); if (sessionData) { sessionData.isShellReady = true; sessionData.shellChannel = mockChannel; } }); it("should send SIGINT as Ctrl+C character", () => { sshManager.sendTerminalSignal("signal-test", "SIGINT"); expect(mockChannel.write).toHaveBeenCalledWith("\x03"); }); it("should send SIGTERM as Ctrl+D character", () => { sshManager.sendTerminalSignal("signal-test", "SIGTERM"); expect(mockChannel.write).toHaveBeenCalledWith("\x04"); }); it("should send SIGTSTP as Ctrl+Z character", () => { sshManager.sendTerminalSignal("signal-test", "SIGTSTP"); expect(mockChannel.write).toHaveBeenCalledWith("\x1A"); }); it("should throw error for unsupported signal", () => { expect(() => { sshManager.sendTerminalSignal("signal-test", "UNSUPPORTED"); }).toThrow("Unsupported signal: UNSUPPORTED"); }); }); describe("resizeTerminal method", () => { beforeEach(async () => { // Setup a test session const config: SSHConnectionConfig = { name: "resize-test", host: "localhost", username: "test", password: "test", }; mockClient.on.mockImplementation((event: string, callback: any) => { if (event === "ready") { setTimeout(callback, 10); } }); const connectionPromise = sshManager.createConnection(config); const readyCallback = mockClient.on.mock.calls.find( (call: any) => call[0] === "ready", )[1]; readyCallback(); await connectionPromise; await new Promise((resolve) => setTimeout(resolve, 20)); // Mock ready state const sessionData = (sshManager as any).connections.get("resize-test"); if (sessionData) { sessionData.isShellReady = true; sessionData.shellChannel = mockChannel; } }); it("should call setWindow with correct dimensions", () => { sshManager.resizeTerminal("resize-test", 80, 24); expect(mockChannel.setWindow).toHaveBeenCalledWith(24, 80, 0, 0); }); it("should validate terminal dimensions", () => { expect(() => { sshManager.resizeTerminal("resize-test", 0, 24); }).toThrow( "Invalid terminal dimensions: cols and rows must be between 1 and 1000", ); expect(() => { sshManager.resizeTerminal("resize-test", 1001, 24); }).toThrow( "Invalid terminal dimensions: cols and rows must be between 1 and 1000", ); expect(() => { sshManager.resizeTerminal("resize-test", 80, 0); }).toThrow( "Invalid terminal dimensions: cols and rows must be between 1 and 1000", ); expect(() => { sshManager.resizeTerminal("resize-test", 80, 1001); }).toThrow( "Invalid terminal dimensions: cols and rows must be between 1 and 1000", ); }); it("should handle resize errors gracefully", () => { mockChannel.setWindow.mockImplementation(() => { throw new Error("Resize failed"); }); expect(() => { sshManager.resizeTerminal("resize-test", 80, 24); }).toThrow("Failed to resize terminal: Resize failed"); }); }); describe("Error handling", () => { it("should throw error when shell not ready", () => { // Create session data but don't mark shell as ready const sessionData = { isShellReady: false, shellChannel: null, connection: { lastActivity: new Date() }, }; (sshManager as any).connections.set("not-ready", sessionData); expect(() => { sshManager.sendTerminalInput("not-ready", "test"); }).toThrow("Shell session not ready for connection 'not-ready'"); expect(() => { sshManager.sendTerminalSignal("not-ready", "SIGINT"); }).toThrow("Shell session not ready for connection 'not-ready'"); expect(() => { sshManager.resizeTerminal("not-ready", 80, 24); }).toThrow("Shell session not ready for connection 'not-ready'"); }); }); });

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