/** * CLIContext Interactive Prompts Tests * * Tests for interactive prompt handling with persistent process */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { CLIContext } from './cli-context'; import { type IntegrationTestContext, setupIntegrationTest } from './integration'; describe('CLIContext - Interactive Prompts', () => { let ctx: IntegrationTestContext; let cli: CLIContext; beforeEach(async () => { ctx = await setupIntegrationTest(); cli = await CLIContext.create('src/cli/index.ts', { CELILO_DB_PATH: ctx.dbPath, CELILO_DATA_DIR: ctx.dataDir, }); }); afterEach(async () => { await cli.dispose(); await ctx.cleanup(); }); describe('output capturing', () => { test('captures command output in buffer', async () => { await cli.run('module list'); // The output should be captured for expectOutput to search await cli.expectOutput(/modules/i, { timeout: 1000 }); }); test('expectOutput waits for pattern to appear', async () => { // Start a command that will produce output const runPromise = cli.run('module list'); // Wait for output pattern await cli.expectOutput(/No modules/i, { timeout: 2000 }); // Command should still complete successfully const result = await runPromise; expect(result.exitCode).toBe(0); }); test('expectOutput throws on timeout if pattern not found', async () => { await cli.run('module list'); // Try to find a pattern that doesn't exist await expect( cli.expectOutput(/THIS_PATTERN_DOES_NOT_EXIST/, { timeout: 500 }), ).rejects.toThrow(/Timeout waiting for pattern/); }); test('expectOutput works with regex patterns', async () => { await cli.run('module list'); // Should match case-insensitive await cli.expectOutput(/MODULES/i, { timeout: 1000 }); }); test('expectOutput works with string patterns', async () => { await cli.run('module list'); // Should match exact string await cli.expectOutput('No modules', { timeout: 1000 }); }); }); describe('ResponseBuilder API', () => { test('on().respond() provides fluent API', () => { // Should return ResponseBuilder const builder = cli.on(/Enter hostname:/); expect(builder).toBeDefined(); expect(typeof builder.respond).toBe('function'); }); test('ResponseBuilder waits for pattern then sends input', async () => { // This test demonstrates the API even though we don't have // a command that actually prompts for input yet // The API should work when we have interactive commands const builder = cli.on(/Would you like to continue/); expect(builder).toBeDefined(); }); }); describe('sendKeys', () => { test('sendKeys writes to process stdin', async () => { // sendKeys should not throw await cli.sendKeys('test input\n'); // If we get here, it didn't throw expect(true).toBe(true); }); test('sendKeys can be called multiple times', async () => { await cli.sendKeys('line 1\n'); await cli.sendKeys('line 2\n'); await cli.sendKeys('line 3\n'); // If we get here, none of them threw expect(true).toBe(true); }); }); describe('output buffer management', () => { test('output accumulates across multiple commands', async () => { await cli.run('module list'); await cli.run('module list'); // Buffer should contain output from both commands await cli.expectOutput(/modules/i); }); test('can search for patterns from earlier commands', async () => { await cli.run('module list'); await cli.run('system config list'); // Should still find pattern from first command await cli.expectOutput(/modules/i); // And pattern from second command await cli.expectOutput(/config/i); }); }); describe('real-world interactive scenarios', () => { test('handles mixed JSON and non-JSON output', async () => { // The CLI outputs both JSON responses (for protocol) // and regular text (Database initialized, logs, etc.) const result = await cli.run('module list'); // Should get JSON response expect(result.exitCode).toBe(0); // And should be able to find text output await cli.expectOutput(/modules/i); }); test('verbose mode shows all I/O', async () => { cli.setVerbose(true); await cli.run('module list'); // Verbose mode should log but not break functionality await cli.expectOutput(/modules/i); }); }); });