87 lines
3.1 KiB
JavaScript
87 lines
3.1 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFile } from 'node:child_process';
|
|
import { promisify } from 'node:util';
|
|
import { resolve } from 'node:path';
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const HOOK_PATH = resolve(import.meta.dirname, '../../hooks/scripts/post-edit-verify.mjs');
|
|
|
|
/**
|
|
* Run the hook with given stdin input (string).
|
|
* Short timeout — these tests should only exercise fast paths.
|
|
*/
|
|
async function runHook(inputStr, timeout = 10000) {
|
|
try {
|
|
const { stdout } = await execFileAsync('node', [HOOK_PATH], {
|
|
input: inputStr,
|
|
timeout,
|
|
});
|
|
return JSON.parse(stdout || '{}');
|
|
} catch (err) {
|
|
if (err.stdout) {
|
|
try { return JSON.parse(err.stdout); } catch { /* ignore */ }
|
|
}
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// post-edit-verify hook — fast-path tests
|
|
// Tests exercise the early-exit paths (non-config, missing file, invalid input).
|
|
// Scanner execution paths are covered by scanner tests.
|
|
// ========================================
|
|
describe('post-edit-verify hook', () => {
|
|
it('returns {} for non-config files', async () => {
|
|
const result = await runHook(JSON.stringify({ file_path: '/tmp/some-random-file.js' }));
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
it('returns {} for nonexistent config files', async () => {
|
|
const result = await runHook(JSON.stringify({ file_path: '/nonexistent/CLAUDE.md' }));
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
it('returns {} for empty input object', async () => {
|
|
const result = await runHook(JSON.stringify({}));
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
it('returns {} for null file_path', async () => {
|
|
const result = await runHook(JSON.stringify({ file_path: null }));
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
it('handles invalid JSON gracefully', async () => {
|
|
const result = await runHook('not json at all');
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
it('handles empty stdin gracefully', async () => {
|
|
const result = await runHook('');
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
it('exits quickly for non-config files', async () => {
|
|
const start = Date.now();
|
|
await runHook(JSON.stringify({ file_path: '/tmp/nothing.txt' }));
|
|
const elapsed = Date.now() - start;
|
|
// Non-config files exit before any scanner import — should be fast
|
|
assert.ok(elapsed < 5000, `Hook took ${elapsed}ms for non-config file`);
|
|
});
|
|
|
|
it('has correct shebang and is valid Node.js', async () => {
|
|
const { readFile } = await import('node:fs/promises');
|
|
const content = await readFile(HOOK_PATH, 'utf-8');
|
|
assert.ok(content.startsWith('#!/usr/bin/env node'));
|
|
assert.ok(content.includes('readFileSync'));
|
|
assert.ok(content.includes('detectScanner'));
|
|
});
|
|
|
|
it('uses cross-platform rules dir pattern (handles both / and \\)', async () => {
|
|
const { readFile } = await import('node:fs/promises');
|
|
const content = await readFile(HOOK_PATH, 'utf-8');
|
|
// The regex should handle both Unix / and Windows \\ separators
|
|
assert.ok(content.includes('[/\\\\]rules[/\\\\]'), 'RULES_DIR_PATTERN should match both / and \\\\');
|
|
});
|
|
});
|