UNPKG

@meldscience/meld

Version:

pipeable one-shot prompt scripting toolkit

331 lines (279 loc) 8.34 kB
import { spawn } from 'child_process'; import { join } from 'path'; import { writeFileSync, unlinkSync, existsSync } from 'fs'; import { beforeEach, afterEach, describe, it, expect } from 'vitest'; import { vi } from 'vitest'; const CLI_PATH = join(__dirname, '../../bin/oneshot.ts'); const TEST_PROMPT = 'test-prompt.md'; const TEST_OUTPUT = 'test-output.md'; const TEST_SYSTEM = 'test-system.md'; const TEST_VARIATIONS_JSON = 'test-variations.json'; const TEST_VARIATIONS_YAML = 'test-variations.yaml'; // Mock environment variables process.env.ANTHROPIC_API_KEY = 'test-anthropic-key'; process.env.OPENAI_API_KEY = 'test-openai-key'; describe('oneshot CLI', () => { beforeEach(() => { // Create test files writeFileSync(TEST_PROMPT, '# Test Prompt\n\nThis is a test prompt.'); writeFileSync(TEST_SYSTEM, 'You are a helpful assistant.'); writeFileSync(TEST_VARIATIONS_JSON, JSON.stringify(['Perspective 1', 'Perspective 2'])); writeFileSync(TEST_VARIATIONS_YAML, 'architect: "Review as architect"\ndeveloper: "Review as developer"'); }); afterEach(() => { // Cleanup test files [TEST_PROMPT, TEST_OUTPUT, TEST_SYSTEM, TEST_VARIATIONS_JSON, TEST_VARIATIONS_YAML].forEach(file => { if (existsSync(file)) unlinkSync(file); }); }); it('should send a simple prompt to Claude', (done) => { const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', TEST_PROMPT]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support multiple models', (done) => { const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', 'gpt-4', TEST_PROMPT]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); expect(output).toContain('🤖 Sending prompt to gpt-4'); done(); }); }); it('should support inline system prompt', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--system', 'You are a helpful assistant' ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support system prompts from file', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--system-file', TEST_SYSTEM ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support inline variations', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--variations', '["View 1", "View 2"]' ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support variations from JSON file', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--variations-file', TEST_VARIATIONS_JSON ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support variations from YAML file', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--variations-file', TEST_VARIATIONS_YAML ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support multiple iterations', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--iterations', '3' ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(output).toContain('🤖 Sending prompt to claude-3'); done(); }); }); it('should support custom output file', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '-o', TEST_OUTPUT ]); let output = ''; oneshot.stdout.on('data', (data) => { output += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(0); expect(existsSync(TEST_OUTPUT)).toBe(true); expect(output).toContain('📝 Response written to'); done(); }); }); it('should handle missing prompt file error', (done) => { const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', 'nonexistent.md']); let stderr = ''; oneshot.stderr.on('data', (data) => { stderr += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(1); expect(stderr).toContain('ENOENT'); done(); }); }); it('should handle missing system file error', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--system-file', 'nonexistent.md' ]); let stderr = ''; oneshot.stderr.on('data', (data) => { stderr += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(1); expect(stderr).toContain('ENOENT'); done(); }); }); it('should handle missing variations file error', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--variations-file', 'nonexistent.yaml' ]); let stderr = ''; oneshot.stderr.on('data', (data) => { stderr += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(1); expect(stderr).toContain('ENOENT'); done(); }); }); it('should handle invalid JSON variations error', (done) => { const oneshot = spawn('ts-node', [ CLI_PATH, 'claude-3', TEST_PROMPT, '--variations', 'invalid json' ]); let stderr = ''; oneshot.stderr.on('data', (data) => { stderr += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(1); expect(stderr).toContain('JSON'); done(); }); }); it('should handle missing API key error', (done) => { // Temporarily unset API key const oldKey = process.env.ANTHROPIC_API_KEY; delete process.env.ANTHROPIC_API_KEY; const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', TEST_PROMPT]); let stderr = ''; oneshot.stderr.on('data', (data) => { stderr += data.toString(); }); oneshot.on('close', (code) => { // Restore API key process.env.ANTHROPIC_API_KEY = oldKey; expect(code).toBe(1); expect(stderr).toContain('Anthropic API key is required'); done(); }); }); it('should handle unsupported model error', (done) => { const oneshot = spawn('ts-node', [CLI_PATH, 'unsupported-model', TEST_PROMPT]); let stderr = ''; oneshot.stderr.on('data', (data) => { stderr += data.toString(); }); oneshot.on('close', (code) => { expect(code).toBe(1); expect(stderr).toContain('Unsupported model'); done(); }); }); });