import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartshell from '../ts/index.js'; // Helper to check if node-pty is available const isPtyAvailable = async (): Promise => { try { await import('node-pty'); return true; } catch { return false; } }; tap.test('PTY: should handle bash read with prompts correctly', async (tools) => { const ptyAvailable = await isPtyAvailable(); if (!ptyAvailable) { console.log('Skipping PTY test - node-pty not installed'); return; } const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // This test should work with PTY (bash read with prompt) const interactive = await testSmartshell.execInteractiveControlPty("bash -c 'read -p \"Enter name: \" name && echo \"Hello, $name\"'"); // Send input programmatically await interactive.sendLine('TestUser'); // Wait for completion const result = await interactive.finalPromise; expect(result.exitCode).toEqual(0); expect(result.stdout).toContain('Enter name:'); // Prompt should be visible with PTY expect(result.stdout).toContain('Hello, TestUser'); }); tap.test('PTY: should handle terminal colors and escape sequences', async (tools) => { const ptyAvailable = await isPtyAvailable(); if (!ptyAvailable) { console.log('Skipping PTY test - node-pty not installed'); return; } const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // ls --color=auto should produce colors in PTY mode const result = await testSmartshell.execInteractiveControlPty('ls --color=always /tmp'); const finalResult = await result.finalPromise; expect(finalResult.exitCode).toEqual(0); // Check for ANSI escape sequences (colors) in output const hasColors = /\x1b\[[0-9;]*m/.test(finalResult.stdout); expect(hasColors).toEqual(true); }); tap.test('PTY: should handle interactive password prompt simulation', async (tools) => { const ptyAvailable = await isPtyAvailable(); if (!ptyAvailable) { console.log('Skipping PTY test - node-pty not installed'); return; } const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // Simulate a password prompt scenario const interactive = await testSmartshell.execStreamingInteractiveControlPty( "bash -c 'read -s -p \"Password: \" pass && echo && echo \"Got password of length ${#pass}\"'" ); await tools.delayFor(100); await interactive.sendLine('secretpass'); const result = await interactive.finalPromise; expect(result.exitCode).toEqual(0); expect(result.stdout).toContain('Password:'); expect(result.stdout).toContain('Got password of length 10'); }); tap.test('PTY: should handle terminal size options', async (tools) => { const ptyAvailable = await isPtyAvailable(); if (!ptyAvailable) { console.log('Skipping PTY test - node-pty not installed'); return; } const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // Check terminal size using stty const result = await testSmartshell.execInteractiveControlPty('stty size'); const finalResult = await result.finalPromise; expect(finalResult.exitCode).toEqual(0); // Default size should be 30 rows x 120 cols as set in _execCommandPty expect(finalResult.stdout).toContain('30 120'); }); tap.test('PTY: should handle Ctrl+C (SIGINT) properly', async (tools) => { const ptyAvailable = await isPtyAvailable(); if (!ptyAvailable) { console.log('Skipping PTY test - node-pty not installed'); return; } const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // Start a long-running process const streaming = await testSmartshell.execStreamingInteractiveControlPty('sleep 10'); // Send interrupt after a short delay await tools.delayFor(100); await streaming.keyboardInterrupt(); const result = await streaming.finalPromise; // Process should exit with non-zero code due to interrupt expect(result.exitCode).not.toEqual(0); }); tap.test('Regular pipe mode should still work alongside PTY', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // Regular mode should work without PTY const interactive = await testSmartshell.execInteractiveControl('echo "Pipe mode works"'); const result = await interactive.finalPromise; expect(result.exitCode).toEqual(0); expect(result.stdout).toContain('Pipe mode works'); }); export default tap.start();