summaryrefslogtreecommitdiff
path: root/tst/exec.test.ts
blob: 1022ec729858e104bf1588fb0c640fc976bf923d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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<any>();
        const cmd = TestTraceable.of<Command, any>(['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<Command, any>('false', new CollectingTrace<any>());
        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<Array<Command>, any>(
            [
                ['echo', 'a'],
                ['echo', 'b'],
            ],
            new CollectingTrace<any>(),
        );

        const res = await getStdoutMany(cmds);
        expect(res.right().get()).toEqual(['a', 'b']);
        expect(execMock).toHaveBeenCalledTimes(2);
    });
});