Files
2025-11-30 09:02:16 +08:00

227 lines
7.0 KiB
TypeScript

// __tests__/cli.integration.test.ts
import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as os from 'os';
describe('CLI Integration', () => {
let testDir: string;
beforeEach(async () => {
// Create temp directory for each test
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cli-test-'));
});
afterEach(async () => {
// Clean up
await fs.rm(testDir, { recursive: true, force: true });
});
describe('Session Management Mode', () => {
const runCLI = (
args: string[]
): Promise<{ stdout: string; stderr: string; exitCode: number }> => {
return new Promise((resolve, reject) => {
const proc = spawn('node', ['dist/cli.js', ...args], {
cwd: path.resolve(__dirname, '..')
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (data) => {
stdout += data.toString();
});
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
resolve({ stdout, stderr, exitCode: code ?? 0 });
});
proc.on('error', reject);
});
};
test('should set and get active_command', async () => {
// Set
const setResult = await runCLI(['session', 'set', 'active_command', '/execute', testDir]);
expect(setResult.exitCode).toBe(0);
// Get
const getResult = await runCLI(['session', 'get', 'active_command', testDir]);
expect(getResult.exitCode).toBe(0);
expect(getResult.stdout.trim()).toBe('/execute');
});
test('should set and get active_skill', async () => {
// Set
const setResult = await runCLI(['session', 'set', 'active_skill', 'brainstorming', testDir]);
expect(setResult.exitCode).toBe(0);
// Get
const getResult = await runCLI(['session', 'get', 'active_skill', testDir]);
expect(getResult.exitCode).toBe(0);
expect(getResult.stdout.trim()).toBe('brainstorming');
});
test('should append to edited_files', async () => {
// Append
const append1 = await runCLI(['session', 'append', 'edited_files', 'file1.ts', testDir]);
expect(append1.exitCode).toBe(0);
const append2 = await runCLI(['session', 'append', 'edited_files', 'file2.ts', testDir]);
expect(append2.exitCode).toBe(0);
// Check contains
const contains1 = await runCLI(['session', 'contains', 'edited_files', 'file1.ts', testDir]);
expect(contains1.exitCode).toBe(0);
const contains2 = await runCLI(['session', 'contains', 'edited_files', 'file2.ts', testDir]);
expect(contains2.exitCode).toBe(0);
const notContains = await runCLI([
'session',
'contains',
'edited_files',
'file3.ts',
testDir
]);
expect(notContains.exitCode).toBe(1);
});
test('should append to file_extensions', async () => {
// Append
const append1 = await runCLI(['session', 'append', 'file_extensions', 'ts', testDir]);
expect(append1.exitCode).toBe(0);
const append2 = await runCLI(['session', 'append', 'file_extensions', 'js', testDir]);
expect(append2.exitCode).toBe(0);
// Check contains
const contains1 = await runCLI(['session', 'contains', 'file_extensions', 'ts', testDir]);
expect(contains1.exitCode).toBe(0);
const notContains = await runCLI(['session', 'contains', 'file_extensions', 'py', testDir]);
expect(notContains.exitCode).toBe(1);
});
test('should clear session', async () => {
// Set some data
await runCLI(['session', 'set', 'active_command', '/execute', testDir]);
await runCLI(['session', 'append', 'edited_files', 'file1.ts', testDir]);
// Clear
const clearResult = await runCLI(['session', 'clear', testDir]);
expect(clearResult.exitCode).toBe(0);
// Verify cleared
const getResult = await runCLI(['session', 'get', 'active_command', testDir]);
expect(getResult.stdout.trim()).toBe('');
});
test('should reject invalid session keys', async () => {
const result = await runCLI(['session', 'get', 'invalid_key', testDir]);
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain('Invalid session key: invalid_key');
});
test('should reject invalid array keys for append', async () => {
const result = await runCLI(['session', 'append', 'session_id', 'value', testDir]);
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain('Invalid array key');
});
test('should reject setting non-settable keys', async () => {
const result = await runCLI(['session', 'set', 'session_id', 'value', testDir]);
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain('Cannot set session_id');
});
test('should show usage for missing arguments', async () => {
const result = await runCLI(['session']);
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain('Usage:');
});
test('should show error for unknown session command', async () => {
const result = await runCLI(['session', 'unknown', testDir]);
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain('Unknown session command');
});
});
describe('Hook Dispatch Mode', () => {
test('should handle hook dispatch with valid JSON input', (done) => {
const proc = spawn('node', ['dist/cli.js'], {
cwd: path.resolve(__dirname, '..')
});
const input = JSON.stringify({
hook_event_name: 'PostToolUse',
cwd: testDir,
tool_name: 'Edit',
tool_input: {}
});
let stdout = '';
proc.stdout.on('data', (data) => {
stdout += data.toString();
});
proc.on('close', (code) => {
expect(code).toBe(0);
// Should produce empty output or valid JSON
if (stdout.trim()) {
expect(() => JSON.parse(stdout)).not.toThrow();
}
done();
});
proc.stdin.write(input);
proc.stdin.end();
});
test('should handle graceful exit on missing required fields', (done) => {
const proc = spawn('node', ['dist/cli.js'], {
cwd: path.resolve(__dirname, '..')
});
const input = JSON.stringify({
// Missing hook_event_name and cwd
tool_name: 'Edit'
});
proc.on('close', (code) => {
expect(code).toBe(0); // Graceful exit
done();
});
proc.stdin.write(input);
proc.stdin.end();
});
test('should handle invalid JSON input', (done) => {
const proc = spawn('node', ['dist/cli.js'], {
cwd: path.resolve(__dirname, '..')
});
let stderr = '';
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
expect(code).toBe(1);
expect(stderr).toContain('Invalid JSON input');
done();
});
proc.stdin.write('not valid json');
proc.stdin.end();
});
});
});