Skip to main content
Glama
terminal-session-state-manager.test.ts7.9 kB
/** * Terminal Session State Manager - TDD Test Suite * * Tests the core state machine functionality that prevents command duplication */ import { TerminalSessionStateManager, SessionBusyError } from '../src/terminal-session-state-manager.js'; import { beforeEach, describe, it, expect } from '@jest/globals'; describe('TerminalSessionStateManager', () => { let stateManager: TerminalSessionStateManager; const TEST_SESSION = 'test-session'; const TEST_COMMAND = 'echo "test"'; const TEST_COMMAND_ID = 'cmd-123'; beforeEach(() => { stateManager = new TerminalSessionStateManager(); }); describe('Initial State', () => { it('should allow commands on new sessions', () => { expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(true); expect(stateManager.getSessionState(TEST_SESSION)).toBe('WAITING_FOR_COMMAND'); expect(stateManager.getCurrentCommand(TEST_SESSION)).toBeNull(); }); }); describe('Command Execution State Transitions', () => { it('should successfully start command execution', () => { const result = stateManager.startCommandExecution( TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp' ); expect(result).toBe(true); expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(false); expect(stateManager.getSessionState(TEST_SESSION)).toBe('EXECUTING_COMMAND'); const currentCommand = stateManager.getCurrentCommand(TEST_SESSION); expect(currentCommand).toMatchObject({ command: TEST_COMMAND, commandId: TEST_COMMAND_ID, initiator: 'mcp' }); expect(currentCommand?.startTime).toBeGreaterThan(0); }); it('should reject concurrent command execution attempts', () => { // Start first command stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); // Attempt second command should throw SessionBusyError expect(() => { stateManager.startCommandExecution(TEST_SESSION, 'echo "second"', 'cmd-456', 'browser'); }).toThrow(SessionBusyError); // First command should still be executing expect(stateManager.getSessionState(TEST_SESSION)).toBe('EXECUTING_COMMAND'); expect(stateManager.getCurrentCommand(TEST_SESSION)?.commandId).toBe(TEST_COMMAND_ID); }); it('should complete command execution and reset to waiting state', () => { // Start command stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(false); // Complete command stateManager.completeCommandExecution(TEST_SESSION, TEST_COMMAND_ID); // Should return to waiting state expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(true); expect(stateManager.getSessionState(TEST_SESSION)).toBe('WAITING_FOR_COMMAND'); expect(stateManager.getCurrentCommand(TEST_SESSION)).toBeNull(); }); it('should allow sequential command execution after completion', () => { // Execute first command stateManager.startCommandExecution(TEST_SESSION, 'echo "first"', 'cmd-1', 'mcp'); stateManager.completeCommandExecution(TEST_SESSION, 'cmd-1'); // Execute second command const result = stateManager.startCommandExecution(TEST_SESSION, 'echo "second"', 'cmd-2', 'browser'); expect(result).toBe(true); expect(stateManager.getCurrentCommand(TEST_SESSION)?.commandId).toBe('cmd-2'); }); }); describe('Multiple Sessions', () => { it('should manage multiple sessions independently', () => { const session1 = 'session-1'; const session2 = 'session-2'; // Start command in session 1 stateManager.startCommandExecution(session1, 'echo "s1"', 'cmd-s1', 'mcp'); expect(stateManager.canAcceptCommand(session1)).toBe(false); // Session 2 should still be available expect(stateManager.canAcceptCommand(session2)).toBe(true); // Start command in session 2 stateManager.startCommandExecution(session2, 'echo "s2"', 'cmd-s2', 'browser'); expect(stateManager.canAcceptCommand(session2)).toBe(false); // Both sessions should be executing expect(stateManager.getSessionState(session1)).toBe('EXECUTING_COMMAND'); expect(stateManager.getSessionState(session2)).toBe('EXECUTING_COMMAND'); // Complete session 1 command stateManager.completeCommandExecution(session1, 'cmd-s1'); expect(stateManager.canAcceptCommand(session1)).toBe(true); expect(stateManager.canAcceptCommand(session2)).toBe(false); // Still executing }); }); describe('Error Recovery', () => { it('should handle completion of unknown session gracefully', () => { // Should not throw error expect(() => { stateManager.completeCommandExecution('unknown-session', 'cmd-123'); }).not.toThrow(); }); it('should handle command ID mismatch during completion', () => { stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); // Complete with wrong command ID (should not throw, but warn) expect(() => { stateManager.completeCommandExecution(TEST_SESSION, 'wrong-cmd-id'); }).not.toThrow(); // Should still reset to waiting state expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(true); }); it('should force reset stuck sessions', () => { stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(false); // Force reset stateManager.forceResetSession(TEST_SESSION); expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(true); expect(stateManager.getCurrentCommand(TEST_SESSION)).toBeNull(); }); it('should remove session from tracking', () => { stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); stateManager.removeSession(TEST_SESSION); // Session should return to initial state behavior expect(stateManager.canAcceptCommand(TEST_SESSION)).toBe(true); expect(stateManager.getSessionState(TEST_SESSION)).toBe('WAITING_FOR_COMMAND'); }); }); describe('Diagnostic Information', () => { it('should provide diagnostic information for sessions', () => { stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); const diagnostics = stateManager.getDiagnosticInfo(); expect(diagnostics).toHaveLength(1); expect(diagnostics[0]).toMatchObject({ sessionName: TEST_SESSION, state: 'EXECUTING_COMMAND', currentCommand: { command: TEST_COMMAND, commandId: TEST_COMMAND_ID, initiator: 'mcp' } }); expect(diagnostics[0].executionDuration).toBeGreaterThanOrEqual(0); }); it('should provide empty diagnostics for no sessions', () => { const diagnostics = stateManager.getDiagnosticInfo(); expect(diagnostics).toHaveLength(0); }); }); describe('SessionBusyError Details', () => { it('should provide detailed error information', () => { stateManager.startCommandExecution(TEST_SESSION, TEST_COMMAND, TEST_COMMAND_ID, 'mcp'); try { stateManager.startCommandExecution(TEST_SESSION, 'echo "second"', 'cmd-456', 'browser'); fail('Should have thrown SessionBusyError'); } catch (error) { expect(error).toBeInstanceOf(SessionBusyError); expect((error as Error).message).toContain(TEST_SESSION); expect((error as Error).message).toContain(TEST_COMMAND); expect((error as Error).message).toContain('mcp'); } }); }); });

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