From 666674327f009e9b1013218fc384f193b64c6997 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 14 Dec 2025 22:39:18 -0800 Subject: Adds unit tests --- tst/exec.test.ts | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tst/exec.test.ts (limited to 'tst/exec.test.ts') diff --git a/tst/exec.test.ts b/tst/exec.test.ts new file mode 100644 index 0000000..1022ec7 --- /dev/null +++ b/tst/exec.test.ts @@ -0,0 +1,110 @@ +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); + }); +}); -- cgit v1.2.3-70-g09d2