ktg-plugin-marketplace/plugins/config-audit/tests/hooks/post-edit-verify.test.mjs

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 \\\\');
});
});