Files
waggle-os/packages/agent/tests/hook-loader.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

113 lines
3.3 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { HookRegistry } from '../src/hooks.js';
import { loadHooksFromConfig } from '../src/hook-loader.js';
describe('loadHooksFromConfig', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-hooks-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('loads deny hooks from config file', async () => {
const configPath = path.join(tmpDir, 'hooks.json');
fs.writeFileSync(configPath, JSON.stringify({
hooks: {
'pre:tool': [
{ type: 'deny', tools: ['bash'], pattern: 'rm -rf' },
],
},
}));
const registry = new HookRegistry();
await loadHooksFromConfig(configPath, registry);
// Verify a hook was registered by firing pre:tool with matching tool+args
const result = await registry.fire('pre:tool', {
toolName: 'bash',
args: { command: 'rm -rf /' },
});
expect(result.cancelled).toBe(true);
expect(result.reason).toContain('rm -rf');
});
it('deny hook blocks matching tool and args pattern', async () => {
const configPath = path.join(tmpDir, 'hooks.json');
fs.writeFileSync(configPath, JSON.stringify({
hooks: {
'pre:tool': [
{ type: 'deny', tools: ['bash', 'write_file'], pattern: 'secrets' },
],
},
}));
const registry = new HookRegistry();
await loadHooksFromConfig(configPath, registry);
// bash with "secrets" in args — should block
const r1 = await registry.fire('pre:tool', {
toolName: 'bash',
args: { command: 'cat secrets.txt' },
});
expect(r1.cancelled).toBe(true);
// write_file with "secrets" in args — should block
const r2 = await registry.fire('pre:tool', {
toolName: 'write_file',
args: { path: '/tmp/secrets', content: 'data' },
});
expect(r2.cancelled).toBe(true);
});
it('deny hook allows non-matching tool or args', async () => {
const configPath = path.join(tmpDir, 'hooks.json');
fs.writeFileSync(configPath, JSON.stringify({
hooks: {
'pre:tool': [
{ type: 'deny', tools: ['bash'], pattern: 'rm -rf' },
],
},
}));
const registry = new HookRegistry();
await loadHooksFromConfig(configPath, registry);
// Different tool — should allow
const r1 = await registry.fire('pre:tool', {
toolName: 'read_file',
args: { path: '/tmp/rm -rf' },
});
expect(r1.cancelled).toBe(false);
// Same tool but no pattern match — should allow
const r2 = await registry.fire('pre:tool', {
toolName: 'bash',
args: { command: 'ls -la' },
});
expect(r2.cancelled).toBe(false);
});
it('returns silently when config file does not exist', async () => {
const configPath = path.join(tmpDir, 'nonexistent-hooks.json');
const registry = new HookRegistry();
// Should not throw
await loadHooksFromConfig(configPath, registry);
// Registry should have no hooks — fire returns not cancelled
const result = await registry.fire('pre:tool', {
toolName: 'bash',
args: { command: 'rm -rf /' },
});
expect(result.cancelled).toBe(false);
});
});