import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartshell from '../ts/index.js'; tap.test('should handle EPIPE errors gracefully', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); const streaming = await testSmartshell.execStreamingInteractiveControl('head -n 1'); // Send more data after head exits (will cause EPIPE) await streaming.sendLine('Line 1'); // This should not throw even though head has exited let errorThrown = false; try { await streaming.sendLine('Line 2'); await streaming.sendLine('Line 3'); } catch (error) { errorThrown = true; // EPIPE or destroyed stdin is expected } const result = await streaming.finalPromise; expect(result.exitCode).toEqual(0); expect(result.stdout).toContain('Line 1'); }); tap.test('should handle strict mode with non-zero exit codes', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); let errorThrown = false; let errorMessage = ''; try { await testSmartshell.execStrict('exit 42'); } catch (error) { errorThrown = true; errorMessage = error.message; } expect(errorThrown).toBeTrue(); expect(errorMessage).toContain('exited with code 42'); }); tap.test('should handle strict mode with signal termination', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); let errorThrown = false; let errorMessage = ''; try { // Use execSpawn with strict mode and kill it const result = testSmartshell.execSpawn('sleep', ['10'], { strict: true, timeout: 100 // Will cause SIGTERM }); await result; } catch (error) { errorThrown = true; errorMessage = error.message; } expect(errorThrown).toBeTrue(); expect(errorMessage).toContain('terminated by signal'); }); tap.test('execAndWaitForLine with timeout should reject properly', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); let errorThrown = false; let errorMessage = ''; try { await testSmartshell.execAndWaitForLine( 'sleep 5 && echo "Too late"', /Too late/, false, { timeout: 100 } ); } catch (error) { errorThrown = true; errorMessage = error.message; } expect(errorThrown).toBeTrue(); expect(errorMessage).toContain('Timeout waiting for pattern'); }); tap.test('execAndWaitForLine with terminateOnMatch should stop process', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); const start = Date.now(); await testSmartshell.execAndWaitForLine( 'echo "Match this" && sleep 5', /Match this/, false, { terminateOnMatch: true } ); const duration = Date.now() - start; // Should terminate immediately after match, not wait for sleep expect(duration).toBeLessThan(2000); }); tap.test('should handle process ending without matching pattern', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); let errorThrown = false; let errorMessage = ''; try { await testSmartshell.execAndWaitForLine( 'echo "Wrong text"', /Never appears/, false ); } catch (error) { errorThrown = true; errorMessage = error.message; } expect(errorThrown).toBeTrue(); expect(errorMessage).toContain('Process ended without matching pattern'); }); tap.test('passthrough unpipe should handle destroyed stdin gracefully', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // This should complete without throwing even if stdin operations fail const result = await testSmartshell.execPassthrough('echo "Test passthrough unpipe"'); expect(result.exitCode).toEqual(0); expect(result.stdout).toContain('Test passthrough unpipe'); }); tap.test('should handle write after stream destroyed', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); const interactive = await testSmartshell.execInteractiveControl('true'); // Exits immediately // Wait for process to exit await interactive.finalPromise; // Try to write after process has exited let errorThrown = false; try { await interactive.sendLine('This should fail'); } catch (error) { errorThrown = true; expect(error.message).toContain('destroyed'); } expect(errorThrown).toBeTrue(); }); tap.test('debug mode should log additional information', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // Capture console.log output const originalLog = console.log; let debugOutput = ''; console.log = (msg: string) => { debugOutput += msg + '\n'; }; try { const streaming = await testSmartshell.execSpawnStreaming('echo', ['Debug test'], { debug: true }); await streaming.terminate(); await streaming.finalPromise; } finally { console.log = originalLog; } // Should have logged debug messages expect(debugOutput).toContain('[smartshell]'); }); tap.test('custom environment variables should be passed correctly', async () => { const testSmartshell = new smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); const result = await testSmartshell.execSpawn('bash', ['-c', 'echo $CUSTOM_VAR'], { env: { ...process.env, CUSTOM_VAR: 'test_value_123' } }); expect(result.exitCode).toEqual(0); expect(result.stdout).toContain('test_value_123'); }); export default tap.start();