Skip to content

Commit 9191141

Browse files
Improve coverage: unit tests for readStdinJson / readRawStdin
1 parent 3797db6 commit 9191141

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

tests/unit/hook-stdin.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* SonarQube CLI
3+
* Copyright (C) 2026 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
21+
import { describe, it, expect, beforeEach, afterEach, spyOn } from 'bun:test';
22+
import { readStdinJson } from '../../src/cli/commands/hook/stdin';
23+
24+
describe('readStdinJson', () => {
25+
type StdinListener = (...args: unknown[]) => void;
26+
const listeners: Record<string, StdinListener[]> = {};
27+
let onSpy: ReturnType<typeof spyOn>;
28+
29+
function captureListener(event: string, fn: StdinListener) {
30+
if (!listeners[event]) listeners[event] = [];
31+
listeners[event].push(fn);
32+
return process.stdin;
33+
}
34+
35+
function emitStdin(event: string, ...args: unknown[]) {
36+
for (const fn of listeners[event] ?? []) {
37+
fn(...args);
38+
}
39+
}
40+
41+
beforeEach(() => {
42+
for (const key of Object.keys(listeners)) {
43+
delete listeners[key];
44+
}
45+
onSpy = spyOn(process.stdin, 'on').mockImplementation(
46+
captureListener as Parameters<typeof spyOn>[2],
47+
);
48+
});
49+
50+
afterEach(() => {
51+
onSpy.mockRestore();
52+
});
53+
54+
it('parses a JSON object from stdin', async () => {
55+
const payload = { tool_name: 'Read', tool_input: { file_path: '/tmp/test.ts' } };
56+
const promise = readStdinJson<typeof payload>();
57+
emitStdin('data', Buffer.from(JSON.stringify(payload)));
58+
emitStdin('end');
59+
expect(await promise).toEqual(payload);
60+
});
61+
62+
it('assembles multiple data chunks before parsing', async () => {
63+
const payload = { a: 1, b: 'hello' };
64+
const json = JSON.stringify(payload);
65+
const mid = Math.floor(json.length / 2);
66+
const promise = readStdinJson<typeof payload>();
67+
emitStdin('data', Buffer.from(json.slice(0, mid)));
68+
emitStdin('data', Buffer.from(json.slice(mid)));
69+
emitStdin('end');
70+
expect(await promise).toEqual(payload);
71+
});
72+
73+
it('throws when stdin contains invalid JSON', async () => {
74+
const promise = readStdinJson();
75+
emitStdin('data', Buffer.from('not-valid-json'));
76+
emitStdin('end');
77+
const err = await promise.catch((e: unknown) => e);
78+
expect(err).toBeInstanceOf(Error);
79+
expect((err as Error).message).toBe('Failed to parse stdin as JSON');
80+
});
81+
82+
it('throws when stdin emits an error event', async () => {
83+
const promise = readStdinJson();
84+
emitStdin('error', new Error('stdin read failed'));
85+
const err = await promise.catch((e: unknown) => e);
86+
expect(err).toBeInstanceOf(Error);
87+
expect((err as Error).message).toBe('stdin read failed');
88+
});
89+
});

0 commit comments

Comments
 (0)