Something went wrong. Try again.
WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage fro
Something went wrong. Try again.
5.8 kB · 167 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168import { EventEmitter } from 'node:events';import * as fs from 'node:fs/promises';import * as path from 'node:path';import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';import { readBodyInput } from '../../src/utils/body-input.js';
// Mutable reference updated per stdin test; null means fall through to real stdinlet currentMockStdin: (EventEmitter & { resume: () => void }) | null = null;
// Mock node:process so process.stdin can be swapped per test without needing// to redefine the non-configurable property on the real process objectvi.mock('node:process', async (importOriginal) => { const actual = await importOriginal<typeof import('node:process')>(); return new Proxy(actual as object, { get(target, prop, receiver) { if (prop === 'stdin' && currentMockStdin !== null) { return currentMockStdin; } return Reflect.get(target, prop, receiver); }, });});
describe('readBodyInput', () => { describe('direct string input', () => { it('should return body string when provided', async () => { const result = await readBodyInput('Test body content'); expect(result).toBe('Test body content'); });
it('should return multiline body string', async () => { const multiline = 'Line 1\nLine 2\nLine 3'; const result = await readBodyInput(multiline); expect(result).toBe(multiline); });
it('should return empty string', async () => { const result = await readBodyInput(''); expect(result).toBe(''); }); });
describe('file input', () => { let tempDir: string; let testFile: string;
beforeEach(async () => { // Create a temporary directory for test files tempDir = path.join(process.cwd(), 'tests', 'fixtures', 'temp'); await fs.mkdir(tempDir, { recursive: true }); testFile = path.join(tempDir, 'test-body.txt'); });
afterEach(async () => { // Clean up test files try { await fs.rm(tempDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } });
it('should read content from file', async () => { const content = 'File content here'; await fs.writeFile(testFile, content, 'utf-8');
const result = await readBodyInput(undefined, testFile); expect(result).toBe(content); });
it('should read multiline content from file', async () => { const content = 'Line 1\nLine 2\nLine 3'; await fs.writeFile(testFile, content, 'utf-8');
const result = await readBodyInput(undefined, testFile); expect(result).toBe(content); });
it('should read empty file', async () => { await fs.writeFile(testFile, '', 'utf-8');
const result = await readBodyInput(undefined, testFile); expect(result).toBe(''); });
it('should throw error when file does not exist', async () => { const nonExistentFile = path.join(tempDir, 'does-not-exist.txt');
await expect(readBodyInput(undefined, nonExistentFile)).rejects.toThrow( `File not found: ${nonExistentFile}` ); });
it('should throw error when path is a directory', async () => { await expect(readBodyInput(undefined, tempDir)).rejects.toThrow( `'${tempDir}' is a directory, not a file` ); }); });
describe('stdin input', () => { afterEach(() => { currentMockStdin = null; });
it('should read content from stdin when - is provided', async () => { const mockStdin = Object.assign(new EventEmitter(), { resume: vi.fn() }); currentMockStdin = mockStdin;
// readBodyInput registers handlers synchronously inside the Promise // constructor before returning, so we can emit immediately after const readPromise = readBodyInput(undefined, '-'); mockStdin.emit('data', Buffer.from('hello from stdin')); mockStdin.emit('end');
expect(await readPromise).toBe('hello from stdin'); });
it('should concatenate multiple chunks from stdin', async () => { const mockStdin = Object.assign(new EventEmitter(), { resume: vi.fn() }); currentMockStdin = mockStdin;
const readPromise = readBodyInput(undefined, '-'); mockStdin.emit('data', Buffer.from('chunk1')); mockStdin.emit('data', Buffer.from(' chunk2')); mockStdin.emit('end');
expect(await readPromise).toBe('chunk1 chunk2'); });
it('should throw when stdin emits an error', async () => { const mockStdin = Object.assign(new EventEmitter(), { resume: vi.fn() }); currentMockStdin = mockStdin;
const readPromise = readBodyInput(undefined, '-'); mockStdin.emit('error', new Error('read error'));
await expect(readPromise).rejects.toThrow('Failed to read from stdin: read error'); }); });
describe('no input', () => { it('should return undefined when no input provided', async () => { const result = await readBodyInput(); expect(result).toBeUndefined(); });
it('should return undefined when both params are undefined', async () => { const result = await readBodyInput(undefined, undefined); expect(result).toBeUndefined(); }); });
describe('error cases', () => { it('should throw error when both bodyString and bodyFilePath provided', async () => { await expect(readBodyInput('body text', '/path/to/file')).rejects.toThrow( 'Cannot specify both --body and --body-file. Choose one input method.' ); });
it('should throw error when both bodyString and stdin flag provided', async () => { await expect(readBodyInput('body text', '-')).rejects.toThrow( 'Cannot specify both --body and --body-file. Choose one input method.' ); }); });});