import { describe, expect, test } from 'bun:test'; async function runGeminiHook( input: object, ): Promise<{ stdout: string; stderr: string; exitCode: number }> { const proc = Bun.spawn(['bun', 'src/bin/cc-safety-net.ts', '-gc'], { stdin: 'pipe', stdout: 'pipe', stderr: 'pipe', }); proc.stdin.write(JSON.stringify(input)); proc.stdin.end(); const stdoutPromise = new Response(proc.stdout).text(); const stderrPromise = new Response(proc.stderr).text(); const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]); const exitCode = await proc.exited; return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode }; } describe('Gemini CLI hook', () => { describe('input parsing', () => { test('blocks rm -rf via run_shell_command', async () => { const input = { hook_event_name: 'BeforeTool', tool_name: 'run_shell_command', tool_input: { command: 'rm -rf /' }, }; const { stdout, exitCode } = await runGeminiHook(input); expect(exitCode).toBe(0); const output = JSON.parse(stdout); expect(output.decision).toBe('deny'); expect(output.reason).toContain('rm -rf'); }); test('allows safe commands (no output)', async () => { const input = { hook_event_name: 'BeforeTool', tool_name: 'run_shell_command', tool_input: { command: 'ls -la' }, }; const { stdout, exitCode } = await runGeminiHook(input); expect(exitCode).toBe(0); expect(stdout).toBe(''); // No output means allowed }); test('ignores non-BeforeTool events', async () => { const input = { hook_event_name: 'AfterTool', tool_name: 'run_shell_command', tool_input: { command: 'rm -rf /' }, }; const { stdout, exitCode } = await runGeminiHook(input); expect(exitCode).toBe(0); expect(stdout).toBe(''); // Ignored, not blocked }); test('ignores non-shell tools', async () => { const input = { hook_event_name: 'BeforeTool', tool_name: 'write_file', tool_input: { path: '/etc/passwd' }, }; const { stdout, exitCode } = await runGeminiHook(input); expect(exitCode).toBe(0); expect(stdout).toBe(''); // Ignored, not blocked }); }); describe('output format', () => { test('outputs Gemini format with decision: deny', async () => { const input = { hook_event_name: 'BeforeTool', tool_name: 'run_shell_command', tool_input: { command: 'git reset --hard' }, }; const { stdout, exitCode } = await runGeminiHook(input); expect(exitCode).toBe(0); const output = JSON.parse(stdout); expect(output).toHaveProperty('decision', 'deny'); expect(output).toHaveProperty('reason'); expect(output.reason).toContain('git reset --hard'); }); }); });