Initial commit
This commit is contained in:
226
hooks/hooks-app/__tests__/cli.integration.test.ts
Normal file
226
hooks/hooks-app/__tests__/cli.integration.test.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
// __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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user