Download packages/cli/src/utils/readStdin.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 5.52 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/utils/readStdin.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/utils/readStdin.test.ts
-
curl -L -o readStdin.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/utils/readStdin.test.ts
5.52 kB
| /** | |
| * @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 { debugLogger } from '@google/gemini-cli-core'; | |
| vi.mock('@google/gemini-cli-core', () => ({ | |
| debugLogger: { | |
| warn: vi.fn(), | |
| }, | |
| })); | |
| // Mock process.stdin | |
| const mockStdin = { | |
| setEncoding: vi.fn(), | |
| read: vi.fn(), | |
| on: vi.fn(), | |
| removeListener: vi.fn(), | |
| destroy: vi.fn(), | |
| listeners: vi.fn().mockReturnValue([]), | |
| listenerCount: vi.fn().mockReturnValue(0), | |
| }; | |
| describe('readStdin', () => { | |
| let originalStdin: typeof process.stdin; | |
| let onReadableHandler: () => void; | |
| let onEndHandler: () => void; | |
| let onErrorHandler: (err: Error) => void; | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| originalStdin = process.stdin; | |
| // Replace process.stdin with our mock | |
| Object.defineProperty(process, 'stdin', { | |
| value: mockStdin, | |
| writable: true, | |
| configurable: true, | |
| }); | |
| // Capture event handlers | |
| mockStdin.on.mockImplementation( | |
| (event: string, handler: (...args: unknown[]) => void) => { | |
| if (event === 'readable') onReadableHandler = handler as () => void; | |
| if (event === 'end') onEndHandler = handler as () => void; | |
| if (event === 'error') onErrorHandler = handler as (err: Error) => void; | |
| }, | |
| ); | |
| mockStdin.listeners.mockReturnValue([]); | |
| mockStdin.listenerCount.mockReturnValue(0); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| Object.defineProperty(process, 'stdin', { | |
| value: originalStdin, | |
| writable: true, | |
| configurable: true, | |
| }); | |
| }); | |
| it('should read and accumulate data from stdin', async () => { | |
| mockStdin.read | |
| .mockReturnValueOnce('I love ') | |
| .mockReturnValueOnce('Gemini!') | |
| .mockReturnValueOnce(null); | |
| const promise = readStdin(); | |
| // Trigger readable event | |
| onReadableHandler(); | |
| // Trigger end to resolve | |
| onEndHandler(); | |
| await expect(promise).resolves.toBe('I love Gemini!'); | |
| }); | |
| it('should handle empty stdin input', async () => { | |
| mockStdin.read.mockReturnValue(null); | |
| const promise = readStdin(); | |
| // Trigger end immediately | |
| onEndHandler(); | |
| await expect(promise).resolves.toBe(''); | |
| }); | |
| // Emulate terminals where stdin is not TTY (eg: git bash) | |
| it('should timeout and resolve with empty string when no input is available', async () => { | |
| vi.useFakeTimers(); | |
| const promise = readStdin(); | |
| // Fast-forward past the timeout (to run test faster) | |
| vi.advanceTimersByTime(500); | |
| await expect(promise).resolves.toBe(''); | |
| vi.useRealTimers(); | |
| }); | |
| it('should clear timeout once when data is received and resolve with data', async () => { | |
| const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout'); | |
| mockStdin.read | |
| .mockReturnValueOnce('chunk1') | |
| .mockReturnValueOnce('chunk2') | |
| .mockReturnValueOnce(null); | |
| const promise = readStdin(); | |
| // Trigger readable event | |
| onReadableHandler(); | |
| expect(clearTimeoutSpy).toHaveBeenCalledOnce(); | |
| // Trigger end to resolve | |
| onEndHandler(); | |
| await expect(promise).resolves.toBe('chunk1chunk2'); | |
| }); | |
| it('should truncate input if it exceeds MAX_STDIN_SIZE', async () => { | |
| const MAX_STDIN_SIZE = 8 * 1024 * 1024; | |
| const largeChunk = 'a'.repeat(MAX_STDIN_SIZE + 100); | |
| mockStdin.read.mockReturnValueOnce(largeChunk).mockReturnValueOnce(null); | |
| const promise = readStdin(); | |
| onReadableHandler(); | |
| await expect(promise).resolves.toBe('a'.repeat(MAX_STDIN_SIZE)); | |
| expect(debugLogger.warn).toHaveBeenCalledWith( | |
| `Warning: stdin input truncated to ${MAX_STDIN_SIZE} bytes.`, | |
| ); | |
| expect(mockStdin.destroy).toHaveBeenCalled(); | |
| }); | |
| it('should truncate multi-byte characters at byte boundary', async () => { | |
| const MAX_STDIN_SIZE = 8 * 1024 * 1024; | |
| // '한' is 3 bytes. 2,796,202 * 3 = 8,388,606 bytes. | |
| // 2,796,203 * 3 = 8,388,609 bytes. | |
| const charCount = Math.floor(MAX_STDIN_SIZE / 3) + 1; | |
| const multiByteChunk = '한'.repeat(charCount); | |
| mockStdin.read | |
| .mockReturnValueOnce(multiByteChunk) | |
| .mockReturnValueOnce(null); | |
| const promise = readStdin(); | |
| onReadableHandler(); | |
| const result = await promise; | |
| const resultBytes = Buffer.byteLength(result, 'utf8'); | |
| expect(resultBytes).toBeLessThanOrEqual(MAX_STDIN_SIZE); | |
| expect(resultBytes).toBe(Math.floor(MAX_STDIN_SIZE / 3) * 3); | |
| expect(result).not.toContain('\uFFFD'); // No replacement characters | |
| }); | |
| it('should use byte length instead of string length for limit', async () => { | |
| const MAX_STDIN_SIZE = 8 * 1024 * 1024; | |
| // '한' is 3 bytes. If we use string length, we'd allow 8M characters = 24MB. | |
| // We want to ensure it stops at 8MB. | |
| const charCount = MAX_STDIN_SIZE; // 8M characters = 24MB | |
| const multiByteChunk = '한'.repeat(charCount); | |
| mockStdin.read | |
| .mockReturnValueOnce(multiByteChunk) | |
| .mockReturnValueOnce(null); | |
| const promise = readStdin(); | |
| onReadableHandler(); | |
| const result = await promise; | |
| expect(Buffer.byteLength(result, 'utf8')).toBeLessThanOrEqual( | |
| MAX_STDIN_SIZE, | |
| ); | |
| expect(result.length).toBeLessThan(charCount); | |
| }); | |
| it('should handle stdin error', async () => { | |
| const promise = readStdin(); | |
| const error = new Error('stdin error'); | |
| onErrorHandler(error); | |
| await expect(promise).rejects.toThrow('stdin error'); | |
| }); | |
| }); | |