Skip to main content
Glama
story-01-nuclear-fallback-timeout-detection.test.ts9.69 kB
/** * Nuclear Fallback Epic - Story 01: Cancellation Timeout Detection * * Test Requirements: * - 30-second timeout timer starts on all cancellation commands * - Timeout detection triggers nuclear fallback protocol * - Successful cancellations clear timeout timers properly * - Multiple sessions handle timeouts independently * * ANTI-MOCK COMPLIANCE: Uses real SSH connections and infrastructure */ import { SSHConnectionManager } from '../src/ssh-connection-manager.js'; describe('Story 01: Nuclear Fallback Timeout Detection', () => { let manager: SSHConnectionManager; beforeEach(() => { manager = new SSHConnectionManager(8080); }); afterEach(async () => { // Clean up all connections with timeout protection const sessions = manager.listSessions(); const disconnectionPromises = sessions.map(async (session) => { try { // Add timeout protection for disconnection const disconnectPromise = manager.disconnectSession(session.name); const timeoutPromise = new Promise<void>((_, reject) => { setTimeout(() => { reject(new Error(`Session ${session.name} disconnection timed out after 5 seconds`)); }, 5000); }); await Promise.race([disconnectPromise, timeoutPromise]); } catch (error) { console.warn(`Failed to disconnect session ${session.name}:`, error); } }); await Promise.allSettled(disconnectionPromises); // Force cleanup of manager resources manager.cleanup(); }); describe('AC 1.1: Timeout Timer Implementation', () => { it('should fail - nuclear timeout timer must start when browser cancellation issued', async () => { // Create real SSH session await manager.createConnection({ name: 'test-session', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Add browser command to buffer manager.addBrowserCommand('test-session', 'sleep 5', 'cmd1', 'user'); // Issue browser cancellation (SIGINT) - should start nuclear timeout timer manager.sendTerminalSignal('test-session', 'SIGINT'); // TEST ASSERTION: Nuclear timeout timer should be active expect(manager.hasActiveNuclearTimeout('test-session')).toBe(true); expect(manager.getNuclearTimeoutDuration('test-session')).toBe(30000); }, 10000); it('should fail - nuclear timeout timer must start when MCP cancellation issued', async () => { // Create real SSH session await manager.createConnection({ name: 'test-session', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Add MCP command to buffer manager.addBrowserCommand('test-session', 'sleep 5', 'cmd1', 'claude'); // Issue MCP cancellation - should start nuclear timeout timer const cancelResult = manager.cancelMCPCommands('test-session'); // TEST ASSERTION: Nuclear timeout timer should be active expect(manager.hasActiveNuclearTimeout('test-session')).toBe(true); expect(manager.getNuclearTimeoutDuration('test-session')).toBe(30000); expect(cancelResult.success).toBe(true); }, 10000); }); describe('AC 1.2: Timeout Detection Triggering', () => { it('should fail - nuclear fallback must trigger after 30 seconds timeout', async () => { // Create real SSH session await manager.createConnection({ name: 'test-session', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Add command and issue cancellation manager.addBrowserCommand('test-session', 'sleep 60', 'cmd1', 'user'); manager.sendTerminalSignal('test-session', 'SIGINT'); // Mock timer acceleration for testing await manager.setNuclearTimeoutDuration(1000); // 1 second for test await new Promise(resolve => setTimeout(resolve, 1500)); // TEST ASSERTION: Nuclear fallback should have triggered expect(manager.hasTriggeredNuclearFallback('test-session')).toBe(true); expect(manager.getLastNuclearFallbackReason('test-session')).toContain('timeout'); }, 15000); }); describe('AC 1.3: Successful Cancellation Timer Cleanup', () => { it('should fail - nuclear timeout must clear when manually cleared', async () => { // Create real SSH session await manager.createConnection({ name: 'test-session', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Add browser command and issue cancellation to start nuclear timeout manager.addBrowserCommand('test-session', 'sleep 30', 'cmd1', 'user'); manager.sendTerminalSignal('test-session', 'SIGINT'); // Verify timer started expect(manager.hasActiveNuclearTimeout('test-session')).toBe(true); // Manually clear the nuclear timeout (simulating successful cancellation) manager.clearNuclearTimeout('test-session'); // TEST ASSERTION: Nuclear timeout should be cleared expect(manager.hasActiveNuclearTimeout('test-session')).toBe(false); }, 5000); it('should fail - nuclear timeout must be preserved until explicitly cleared', async () => { // Create real SSH session await manager.createConnection({ name: 'test-session', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Add browser command and issue cancellation to start nuclear timeout manager.addBrowserCommand('test-session', 'sleep 10', 'cmd1', 'user'); manager.sendTerminalSignal('test-session', 'SIGINT'); // Verify timer started expect(manager.hasActiveNuclearTimeout('test-session')).toBe(true); // Wait some time - timeout should remain active await new Promise(resolve => setTimeout(resolve, 500)); // TEST ASSERTION: Nuclear timeout should still be active expect(manager.hasActiveNuclearTimeout('test-session')).toBe(true); // Execute a different command that completes successfully await manager.executeCommand('test-session', 'echo "separate command"', { timeout: 2000, source: 'claude' }); // TEST ASSERTION: Nuclear timeout should be cleared after any successful command completion expect(manager.hasActiveNuclearTimeout('test-session')).toBe(false); }, 8000); }); describe('AC 1.4: Multiple Session Timeout Support', () => { it('should fail - each session must handle nuclear timeouts independently', async () => { // Create multiple real SSH sessions await manager.createConnection({ name: 'session-1', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); await manager.createConnection({ name: 'session-2', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Issue cancellations for both sessions manager.addBrowserCommand('session-1', 'sleep 30', 'cmd1', 'user'); manager.addBrowserCommand('session-2', 'sleep 30', 'cmd2', 'user'); manager.sendTerminalSignal('session-1', 'SIGINT'); manager.sendTerminalSignal('session-2', 'SIGINT'); // TEST ASSERTION: Both sessions should have independent timeouts expect(manager.hasActiveNuclearTimeout('session-1')).toBe(true); expect(manager.hasActiveNuclearTimeout('session-2')).toBe(true); // Clear timeout for session-1 only manager.clearNuclearTimeout('session-1'); // TEST ASSERTION: Only session-1 timeout should be cleared expect(manager.hasActiveNuclearTimeout('session-1')).toBe(false); expect(manager.hasActiveNuclearTimeout('session-2')).toBe(true); }, 15000); }); describe('Integration: Complete Timeout Detection Flow', () => { it('should fail - complete nuclear timeout workflow with real SSH session', async () => { // Create real SSH session await manager.createConnection({ name: 'integration-test', host: 'localhost', username: 'jsbattig', keyFilePath: '/home/jsbattig/.ssh/id_ed25519' }); // Add both browser and MCP commands manager.addBrowserCommand('integration-test', 'sleep 45', 'browser-cmd', 'user'); manager.addBrowserCommand('integration-test', 'sleep 45', 'mcp-cmd', 'claude'); // Issue browser cancellation manager.sendTerminalSignal('integration-test', 'SIGINT'); // Verify nuclear timeout started and is tracking properly expect(manager.hasActiveNuclearTimeout('integration-test')).toBe(true); expect(manager.getNuclearTimeoutStartTime('integration-test')).toBeGreaterThan(Date.now() - 5000); // Accelerate timeout for testing await manager.setNuclearTimeoutDuration(500); // Wait longer for nuclear fallback to complete fully await new Promise(resolve => setTimeout(resolve, 2000)); // TEST ASSERTIONS: Complete nuclear fallback workflow expect(manager.hasTriggeredNuclearFallback('integration-test')).toBe(true); expect(manager.isSessionHealthy('integration-test')).toBe(true); // Should be healthy after fallback expect(manager.getBrowserCommandBuffer('integration-test')).toHaveLength(0); // Buffer cleared }, 20000); }); });

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