import { describe, it, expect } from '@jest/globals'; import { runCLIProcess } from '../src/providers/cli/process-runner'; describe('runCLIProcess', () => { it('should execute a simple command and return stdout', async () => { const result = await runCLIProcess({ command: 'echo', args: ['hello world'], }); expect(result.stdout.trim()).toBe('hello world'); expect(result.exitCode).toBe(0); }); it('should reject on non-zero exit code', async () => { await expect( runCLIProcess({ command: 'bash', args: ['-c', 'exit 1'], }) ).rejects.toThrow(/exited with code 1/); }); it('should reject on timeout', async () => { await expect( runCLIProcess({ command: 'sleep', args: ['10'], timeout: 100, }) ).rejects.toThrow(/timed out/); }, 10000); it('should pipe stdin correctly', async () => { const result = await runCLIProcess({ command: 'cat', args: [], stdin: 'hello from stdin', }); expect(result.stdout).toBe('hello from stdin'); }); it('should pass environment variables to subprocess', async () => { const result = await runCLIProcess({ command: 'bash', args: ['-c', 'echo $MY_TEST_VAR'], env: { MY_TEST_VAR: 'test-value' }, }); expect(result.stdout.trim()).toBe('test-value'); }); it('should apply working directory', async () => { const result = await runCLIProcess({ command: 'pwd', args: [], cwd: '/tmp', }); // /tmp might resolve to /private/tmp on macOS expect(result.stdout.trim()).toMatch(/\/tmp$/); }); it('should capture stderr', async () => { await expect( runCLIProcess({ command: 'bash', args: ['-c', 'echo "error msg" >&2; exit 1'], }) ).rejects.toThrow(/error msg/); }); it('should reject on command not found', async () => { await expect( runCLIProcess({ command: 'nonexistent-command-that-does-not-exist', args: [], }) ).rejects.toThrow(/Failed to spawn/); }); it('should handle process that dies before consuming stdin', async () => { // Process exits immediately without reading stdin — stdin write error // should be swallowed, and the real exit code error surfaces instead await expect( runCLIProcess({ command: 'bash', args: ['-c', 'exit 42'], stdin: 'data that will not be read', }) ).rejects.toThrow(/exited with code 42/); }); it('should cap stdout buffer for large output', async () => { // Generate ~200KB of output — well under the 10MB cap but enough // to verify buffering works. (Testing the actual 10MB cap would be // too slow for a unit test.) const result = await runCLIProcess({ command: 'bash', args: ['-c', 'for i in $(seq 1 5000); do echo "line $i padding text to make it longer"; done'], }); expect(result.stdout.length).toBeGreaterThan(0); expect(result.exitCode).toBe(0); }); it('should handle process killed by signal', async () => { // Process sends itself SIGTERM await expect( runCLIProcess({ command: 'bash', args: ['-c', 'kill -TERM $$'], }) ).rejects.toThrow(/exited with code/); }); });