import { EventEmitter } from 'node:events'; import type { Command } from '../lib/index'; import { CollectingTrace, TestTraceable } from './test_utils'; jest.mock('node:child_process', () => ({ exec: jest.fn(), })); const getExecMock = async () => { const mod = await import('node:child_process'); return (mod as any).exec as jest.Mock; }; describe('process/exec (getStdout/getStdoutMany)', () => { let execMock: jest.Mock; beforeEach(async () => { jest.resetModules(); execMock = await getExecMock(); execMock.mockReset(); }); test('getStdout executes command and returns stdout', async () => { const { getStdout } = await import('../lib/index'); const proc: any = new EventEmitter(); proc.stdout = new EventEmitter(); proc.stderr = new EventEmitter(); execMock.mockReturnValue(proc); const trace = new CollectingTrace(); const cmd = TestTraceable.of(['echo', 'hi'], trace); const p = getStdout(cmd, { env: { FOO: 'bar' }, streamTraceable: ['stdout'] }); proc.stdout.emit('data', Buffer.from('hello')); proc.emit('exit', 0); const res = await p; expect(res.right().get()).toBe('hello'); expect(execMock).toHaveBeenCalledTimes(1); expect(execMock.mock.calls[0][0]).toBe('echo hi'); expect(execMock.mock.calls[0][1]).toEqual( expect.objectContaining({ env: expect.objectContaining({ FOO: 'bar' }) }), ); const flattened = trace.events.flatMap((e) => e); expect(flattened).toEqual(expect.arrayContaining(['hello'])); }); test('getStdout returns left on non-zero exit', async () => { const { getStdout } = await import('../lib/index'); const proc: any = new EventEmitter(); proc.stdout = new EventEmitter(); proc.stderr = new EventEmitter(); execMock.mockReturnValue(proc); const cmd = TestTraceable.of('false', new CollectingTrace()); const p = getStdout(cmd); proc.emit('exit', 2); const res = await p; expect(res.left().present()).toBe(true); expect(res.left().get().message).toMatch(/non-zero/); }); test('getStdoutMany runs commands sequentially', async () => { const { getStdoutMany } = await import('../lib/index'); const makeProc = (out: string) => { const proc: any = new EventEmitter(); proc.stdout = new EventEmitter(); proc.stderr = new EventEmitter(); return proc; }; execMock .mockImplementationOnce(() => { const proc = makeProc('a'); queueMicrotask(() => { proc.stdout.emit('data', Buffer.from('a')); proc.emit('exit', 0); }); return proc; }) .mockImplementationOnce(() => { const proc = makeProc('b'); queueMicrotask(() => { proc.stdout.emit('data', Buffer.from('b')); proc.emit('exit', 0); }); return proc; }); const cmds = TestTraceable.of, any>( [ ['echo', 'a'], ['echo', 'b'], ], new CollectingTrace(), ); const res = await getStdoutMany(cmds); expect(res.right().get()).toEqual(['a', 'b']); expect(execMock).toHaveBeenCalledTimes(2); }); });