test(hooks): cover PostToolUse trigger guards

This commit is contained in:
KumamuKuma
2026-07-28 11:09:18 +08:00
parent 10842f1168
commit 21a7fbb16f
+91 -10
View File
@@ -1,5 +1,11 @@
import { spawnSync } from 'node:child_process'; import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path'; import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
@@ -13,34 +19,65 @@ const hookScript = join(
'hooks', 'hooks',
'post-tool-use-auto-update.mjs', 'post-tool-use-auto-update.mjs',
); );
const hooksConfig = JSON.parse(
readFileSync(
join(repoRoot, 'understand-anything-plugin', 'hooks', 'hooks.json'),
'utf8',
),
);
function runHook({ function runHook({
command = 'git commit -m "test"', command = 'git commit -m "test"',
autoUpdate = true, autoUpdate = true,
configContents,
createConfig = true,
createGraph = true, createGraph = true,
dataDirName = '.understand-anything',
input,
pluginRoot,
} = {}) { } = {}) {
const projectRoot = mkdtempSync(join(tmpdir(), 'ua-post-tool-use-')); const projectRoot = mkdtempSync(join(tmpdir(), 'ua-post-tool-use-'));
const dataDir = join(projectRoot, '.understand-anything'); const dataDir = join(projectRoot, dataDirName);
mkdirSync(dataDir); mkdirSync(dataDir);
writeFileSync(join(dataDir, 'config.json'), JSON.stringify({ autoUpdate })); if (createConfig) {
writeFileSync(
join(dataDir, 'config.json'),
configContents ?? JSON.stringify({ autoUpdate }),
);
}
if (createGraph) writeFileSync(join(dataDir, 'knowledge-graph.json'), '{}'); if (createGraph) writeFileSync(join(dataDir, 'knowledge-graph.json'), '{}');
const pluginRoot = join(projectRoot, 'plugin root'); const resolvedPluginRoot = pluginRoot ?? join(projectRoot, 'plugin root');
const result = spawnSync(process.execPath, [hookScript], { const result = spawnSync(process.execPath, [hookScript], {
cwd: projectRoot, cwd: projectRoot,
input: JSON.stringify({ input:
tool_name: 'Bash', input ??
tool_input: { command }, JSON.stringify({
}), tool_name: 'Bash',
env: { ...process.env, CLAUDE_PLUGIN_ROOT: pluginRoot }, tool_input: { command },
}),
env: { ...process.env, CLAUDE_PLUGIN_ROOT: resolvedPluginRoot },
encoding: 'utf8', encoding: 'utf8',
}); });
rmSync(projectRoot, { recursive: true, force: true }); rmSync(projectRoot, { recursive: true, force: true });
return { ...result, pluginRoot }; return { ...result, pluginRoot: resolvedPluginRoot };
} }
describe('PostToolUse auto-update hook', () => { describe('PostToolUse auto-update hook', () => {
it('is registered as the Bash PostToolUse handler', () => {
const registration = hooksConfig.hooks.PostToolUse[0];
expect(registration.matcher).toBe('Bash');
expect(registration.hooks).toEqual([
{
type: 'command',
command:
'node "${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-auto-update.mjs"',
},
]);
});
it('injects the auto-update instruction as PostToolUse additional context', () => { it('injects the auto-update instruction as PostToolUse additional context', () => {
const result = runHook(); const result = runHook();
const output = JSON.parse(result.stdout); const output = JSON.parse(result.stdout);
@@ -60,6 +97,37 @@ describe('PostToolUse auto-update hook', () => {
); );
}); });
it.each(['commit', 'merge', 'cherry-pick', 'rebase'])(
'recognizes git %s as a graph-changing operation',
(operation) => {
const result = runHook({ command: `git ${operation} example` });
expect(result.status).toBe(0);
expect(
JSON.parse(result.stdout).hookSpecificOutput.hookEventName,
).toBe('PostToolUse');
},
);
it('preserves Windows-style plugin paths as valid JSON', () => {
const pluginRoot = 'C:\\Users\\Example Person\\understand-anything';
const result = runHook({ pluginRoot });
const output = JSON.parse(result.stdout);
expect(output.hookSpecificOutput.additionalContext).toContain(
`${pluginRoot}/hooks/auto-update-prompt.md`,
);
});
it('supports the legacy .ua data directory', () => {
const result = runHook({ dataDirName: '.ua' });
expect(result.status).toBe(0);
expect(JSON.parse(result.stdout).hookSpecificOutput.hookEventName).toBe(
'PostToolUse',
);
});
it('stays silent for unrelated Bash commands', () => { it('stays silent for unrelated Bash commands', () => {
const result = runHook({ command: 'git status --short' }); const result = runHook({ command: 'git status --short' });
@@ -73,4 +141,17 @@ describe('PostToolUse auto-update hook', () => {
expect(result.status).toBe(0); expect(result.status).toBe(0);
expect(result.stdout).toBe(''); expect(result.stdout).toBe('');
}); });
it.each([
['malformed stdin', { input: '{not-json' }],
['a missing config', { createConfig: false }],
['a malformed config', { configContents: '{not-json' }],
['a missing graph', { createGraph: false }],
])('stays silent for %s', (_scenario, options) => {
const result = runHook(options);
expect(result.status).toBe(0);
expect(result.stderr).toBe('');
expect(result.stdout).toBe('');
});
}); });