Skip to main content
Glama
dual-prompt-prevention.test.ts7.05 kB
/** * TEST-DRIVEN DEVELOPMENT: Dual Prompt Prevention Tests * These tests MUST fail initially to drive the implementation of proper shell initialization. * * ROOT CAUSE: SSH shell initialization causes dual prompt generation: * 1. Shell starts with default prompt: jsbattig@localhost:~$ * 2. .bashrc changes PS1 to bracket format: [jsbattig@localhost ~]$ * 3. Both prompts appear in stream: jsbattig@localhost:~[jsbattig@localhost ~]$ * * SOLUTION: Configure SSH shell with consistent PS1 from initialization start */ import { SSHConnectionManager } from '../src/ssh-connection-manager.js'; describe('Dual Prompt Prevention - Root Cause Fix', () => { let manager: SSHConnectionManager; const sessionName = 'dual-prompt-test'; beforeEach(() => { manager = new SSHConnectionManager(); }); afterEach(async () => { if (manager.hasSession(sessionName)) { await manager.disconnectSession(sessionName); } manager.cleanup(); }); test('MUST fail initially: Shell initialization should produce only bracket format prompt', async () => { // This test MUST fail with current implementation that allows dual prompts const connection = await manager.createConnection({ name: sessionName, host: 'localhost', username: 'jsbattig', keyFilePath: '~/.ssh/id_ed25519' }); expect(connection.name).toBe(sessionName); // Wait for shell to fully initialize await new Promise(resolve => setTimeout(resolve, 2000)); // Get complete terminal history from initialization const history = await manager.getTerminalHistory(sessionName); const allOutput = history.map(entry => entry.output).join(''); console.log('Raw shell initialization output:', JSON.stringify(allOutput)); // CRITICAL ASSERTION: Should NEVER contain dual prompt patterns // This will FAIL with current implementation showing we need to fix the source expect(allOutput).not.toMatch(/jsbattig@localhost:~\[jsbattig@localhost ~\]\$/); // POSITIVE ASSERTION: Should only contain clean bracket format const bracketPrompts = allOutput.match(/\[jsbattig@localhost [^\]]+\]\$/g) || []; const oldPrompts = allOutput.match(/jsbattig@localhost:~\$/g) || []; console.log('Bracket prompts found:', bracketPrompts.length); console.log('Old prompts found:', oldPrompts.length); // Should have bracket prompts (the desired format) expect(bracketPrompts.length).toBeGreaterThan(0); // Should have ZERO old format prompts (they should be suppressed during initialization) expect(oldPrompts.length).toBe(0); }); test('MUST fail initially: Commands should only show bracket format prompts', async () => { // This test ensures the fix works for ongoing command execution await manager.createConnection({ name: sessionName, host: 'localhost', username: 'jsbattig', keyFilePath: '~/.ssh/id_ed25519' }); // Wait for shell to initialize await new Promise(resolve => setTimeout(resolve, 2000)); // Clear initial history to focus on command execution const initialHistory = await manager.getTerminalHistory(sessionName); console.log('Initial history length:', initialHistory.length); // Execute a simple command const result = await manager.executeCommand(sessionName, 'echo test-command', { timeout: 5000 }); expect(result.stdout).toBe('test-command'); // Get history from command execution const commandHistory = await manager.getTerminalHistory(sessionName); const recentOutput = commandHistory .slice(initialHistory.length) // Only new entries since command .map(entry => entry.output) .join(''); console.log('Command execution output:', JSON.stringify(recentOutput)); // CRITICAL: Command execution should not introduce dual prompts expect(recentOutput).not.toMatch(/jsbattig@localhost:~\[jsbattig@localhost ~\]\$/); // Should only contain bracket format prompts if any const dualPatterns = recentOutput.match(/[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+:[^[]*\[[^]]+\]\$/g) || []; expect(dualPatterns.length).toBe(0); }); test('MUST fail initially: Generic user/host should not have dual prompt corruption', async () => { // Test that our fix is generic and not hardcoded to specific usernames // This verifies we're fixing the shell initialization process, not just filtering specific patterns // Note: This test uses localhost/jsbattig but validates against generic patterns await manager.createConnection({ name: sessionName, host: 'localhost', username: 'jsbattig', keyFilePath: '~/.ssh/id_ed25519' }); await new Promise(resolve => setTimeout(resolve, 2000)); const history = await manager.getTerminalHistory(sessionName); const allOutput = history.map(entry => entry.output).join(''); // Generic dual prompt pattern: any_user@any_host:path[any_user@any_host dir]$ const genericDualPattern = /([a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+:[~\w/.[\]-]*)(\[[a-zA-Z0-9_.\s-]+@[a-zA-Z0-9_.-]+\s+[^\]]+\]\$)/g; const dualPromptMatches = allOutput.match(genericDualPattern) || []; console.log('Generic dual prompt matches:', dualPromptMatches); // Should have ZERO dual prompt patterns for any user@host combination expect(dualPromptMatches.length).toBe(0); // Should only have clean bracket format prompts const cleanBracketPrompts = allOutput.match(/\[[a-zA-Z0-9_.\s-]+@[a-zA-Z0-9_.-]+\s+[^\]]+\]\$/g) || []; expect(cleanBracketPrompts.length).toBeGreaterThan(0); }); test('MUST fail initially: No prompt corruption in prepareOutputForBrowser should be needed', async () => { // This test validates that we fix the source so prepareOutputForBrowser doesn't need corruption fixes await manager.createConnection({ name: sessionName, host: 'localhost', username: 'jsbattig', keyFilePath: '~/.ssh/id_ed25519' }); await new Promise(resolve => setTimeout(resolve, 2000)); // Get raw output (before prepareOutputForBrowser processing) const history = await manager.getTerminalHistory(sessionName); const output = history.map(entry => entry.output).join(''); console.log('Raw output for corruption analysis:', JSON.stringify(output)); // The raw output itself should be clean - no corruption filtering should be needed // If shell initialization is fixed, raw output won't contain dual prompts expect(output).not.toContain('jsbattig@localhost:~[jsbattig@localhost ~]$'); // Verify that the issue is fixed at the source, not hidden by filtering const corruptionPatterns = [ /jsbattig@localhost:~\[jsbattig@localhost ~\]\$/g, /([a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+:[~\w/.[\]-]*)(\[[a-zA-Z0-9_.\s-]+@[a-zA-Z0-9_.-]+\s+[^\]]+\]\$)/g ]; for (const pattern of corruptionPatterns) { const matches = output.match(pattern) || []; expect(matches.length).toBe(0); } }); });

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