Files
gemini-cli/packages/cli/src/utils/readStdin_safety.test.ts

93 lines
2.5 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest';
import { readStdin } from './readStdin.js';
import { EventEmitter } from 'node:events';
// Mock debugLogger to avoid clutter
vi.mock('@google/gemini-cli-core', () => ({
debugLogger: {
warn: vi.fn(),
},
}));
describe('readStdin EIO Reproduction', () => {
let originalStdin: typeof process.stdin;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let fakeStdin: EventEmitter & { setEncoding: any; read: any; destroy: any };
beforeEach(() => {
originalStdin = process.stdin;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fakeStdin = new EventEmitter() as any;
fakeStdin.setEncoding = vi.fn();
fakeStdin.read = vi.fn().mockReturnValue(null); // Return null to simulate end of reading or no data
fakeStdin.destroy = vi.fn();
Object.defineProperty(process, 'stdin', {
value: fakeStdin,
writable: true,
configurable: true,
});
});
afterEach(() => {
Object.defineProperty(process, 'stdin', {
value: originalStdin,
writable: true,
configurable: true,
});
vi.restoreAllMocks();
});
it('crashes (throws unhandled error) if EIO happens after readStdin completes', async () => {
const promise = readStdin();
fakeStdin.emit('end');
await promise;
// Verify listeners are removed (implementation detail check)
// We expect 1 listener now (our no-op handler) because we started with 0.
expect(fakeStdin.listenerCount('error')).toBe(1);
// This mimics the crash.
// We expect this NOT to throw now that we've added a no-op handler.
expect(() => {
fakeStdin.emit('error', new Error('EIO'));
}).not.toThrow();
});
it('does NOT add a no-op handler if another error listener is present', async () => {
const customErrorHandler = vi.fn();
fakeStdin.on('error', customErrorHandler);
const promise = readStdin();
fakeStdin.emit('end');
await promise;
// It should have exactly 1 listener (our custom one), not 2.
expect(fakeStdin.listenerCount('error')).toBe(1);
expect(fakeStdin.listeners('error')).toContain(customErrorHandler);
// Triggering error should call our handler and NOT crash (because there is a listener)
const error = new Error('EIO');
fakeStdin.emit('error', error);
expect(customErrorHandler).toHaveBeenCalledWith(error);
});
});