Files
everything-claude-code/tests/hooks/doc-file-warning.test.js
Lidang-Jiang 3c3781ca43 refactor: address reviewer feedback
- Add options={} parameter to run() to match run-with-flags.js contract
- Remove case-insensitive flag from extension pre-filter for consistency
  with ADHOC_FILENAMES regex (both now case-sensitive)
- Expand warning text to list more structured paths
- Add test cases for uppercase extensions (TODO.MD, NOTES.TXT)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-29 10:09:02 +08:00

216 lines
7.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const assert = require('assert');
const path = require('path');
const { spawnSync } = require('child_process');
const script = path.join(__dirname, '..', '..', 'scripts', 'hooks', 'doc-file-warning.js');
function test(name, fn) {
try {
fn();
console.log(`${name}`);
return true;
} catch (err) {
console.log(`${name}`);
console.log(` Error: ${err.message}`);
return false;
}
}
function runScript(input) {
const result = spawnSync('node', [script], {
encoding: 'utf8',
input: JSON.stringify(input),
timeout: 10000,
});
return { code: result.status || 0, stdout: result.stdout || '', stderr: result.stderr || '' };
}
function runTests() {
console.log('\n=== Testing doc-file-warning.js (denylist policy) ===\n');
let passed = 0;
let failed = 0;
// 1. Standard doc filenames - never on denylist, no warning
const standardFiles = [
'README.md',
'CLAUDE.md',
'AGENTS.md',
'CONTRIBUTING.md',
'CHANGELOG.md',
'LICENSE.md',
'SKILL.md',
'MEMORY.md',
'WORKLOG.md',
];
for (const file of standardFiles) {
(test(`allows standard doc file: ${file}`, () => {
const { code, stderr } = runScript({ tool_input: { file_path: file } });
assert.strictEqual(code, 0, `expected exit code 0, got ${code}`);
assert.strictEqual(stderr, '', `expected no warning for ${file}, got: ${stderr}`);
}) ? passed++ : failed++);
}
// 2. Structured directory paths - no warning even for ad-hoc names
const structuredDirPaths = [
'docs/foo.md',
'docs/guide/setup.md',
'docs/TODO.md',
'docs/specs/NOTES.md',
'skills/bar.md',
'skills/testing/tdd.md',
'.history/session.md',
'memory/patterns.md',
'.claude/commands/deploy.md',
'.claude/plans/roadmap.md',
'.claude/projects/myproject.md',
'.github/ISSUE_TEMPLATE/bug.md',
'commands/triage.md',
'benchmarks/test.md',
'templates/DRAFT.md',
];
for (const file of structuredDirPaths) {
(test(`allows structured directory path: ${file}`, () => {
const { code, stderr } = runScript({ tool_input: { file_path: file } });
assert.strictEqual(code, 0, `expected exit code 0, got ${code}`);
assert.strictEqual(stderr, '', `expected no warning for ${file}, got: ${stderr}`);
}) ? passed++ : failed++);
}
// 3. Allowed .plan.md files - no warning
(test('allows .plan.md files', () => {
const { code, stderr } = runScript({ tool_input: { file_path: 'feature.plan.md' } });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for .plan.md, got: ${stderr}`);
}) ? passed++ : failed++);
(test('allows nested .plan.md files', () => {
const { code, stderr } = runScript({ tool_input: { file_path: 'src/refactor.plan.md' } });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for nested .plan.md, got: ${stderr}`);
}) ? passed++ : failed++);
// 4. Non-md/txt files always pass - no warning
const nonDocFiles = ['foo.js', 'app.py', 'styles.css', 'data.json', 'image.png'];
for (const file of nonDocFiles) {
(test(`allows non-doc file: ${file}`, () => {
const { code, stderr } = runScript({ tool_input: { file_path: file } });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for ${file}, got: ${stderr}`);
}) ? passed++ : failed++);
}
// 5. Lowercase, partial-match, and non-standard extension case - NOT on denylist
const allowedNonDenylist = [
'random-notes.md',
'notes.txt',
'scratch.md',
'ideas.txt',
'todo-list.md',
'my-draft.md',
'meeting-notes.txt',
'TODO.MD',
'NOTES.TXT',
];
for (const file of allowedNonDenylist) {
(test(`allows non-denylist doc file: ${file}`, () => {
const { code, stderr } = runScript({ tool_input: { file_path: file } });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for ${file}, got: ${stderr}`);
}) ? passed++ : failed++);
}
// 6. Ad-hoc denylist filenames at root/non-structured paths - SHOULD warn
const deniedFiles = [
'NOTES.md',
'TODO.md',
'SCRATCH.md',
'TEMP.md',
'DRAFT.txt',
'BRAINSTORM.md',
'SPIKE.md',
'DEBUG.md',
'WIP.txt',
'src/NOTES.md',
'lib/TODO.txt',
];
for (const file of deniedFiles) {
(test(`warns on ad-hoc denylist file: ${file}`, () => {
const { code, stderr } = runScript({ tool_input: { file_path: file } });
assert.strictEqual(code, 0, 'should still exit 0 (warn only)');
assert.ok(stderr.includes('WARNING'), `expected warning in stderr for ${file}, got: ${stderr}`);
assert.ok(stderr.includes(file), `expected file path in stderr for ${file}`);
}) ? passed++ : failed++);
}
// 7. Windows backslash paths - normalized correctly
(test('allows ad-hoc name in structured dir with backslash path', () => {
const { code, stderr } = runScript({ tool_input: { file_path: 'docs\\specs\\NOTES.md' } });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', 'expected no warning for structured dir with backslash');
}) ? passed++ : failed++);
(test('warns on ad-hoc name with backslash in non-structured dir', () => {
const { code, stderr } = runScript({ tool_input: { file_path: 'src\\SCRATCH.md' } });
assert.strictEqual(code, 0, 'should still exit 0');
assert.ok(stderr.includes('WARNING'), 'expected warning for non-structured backslash path');
}) ? passed++ : failed++);
// 8. Invalid/empty input - passes through without error
(test('handles empty object input without error', () => {
const { code, stderr } = runScript({});
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for empty input, got: ${stderr}`);
}) ? passed++ : failed++);
(test('handles missing file_path without error', () => {
const { code, stderr } = runScript({ tool_input: {} });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for missing file_path, got: ${stderr}`);
}) ? passed++ : failed++);
(test('handles empty file_path without error', () => {
const { code, stderr } = runScript({ tool_input: { file_path: '' } });
assert.strictEqual(code, 0);
assert.strictEqual(stderr, '', `expected no warning for empty file_path, got: ${stderr}`);
}) ? passed++ : failed++);
// 9. Malformed input - passes through without error
(test('handles non-JSON input without error', () => {
const result = spawnSync('node', [script], {
encoding: 'utf8',
input: 'not-json',
timeout: 10000,
});
assert.strictEqual(result.status || 0, 0);
assert.strictEqual(result.stderr || '', '');
assert.strictEqual(result.stdout, 'not-json');
}) ? passed++ : failed++);
// 10. Stdout always contains the original input (pass-through)
(test('passes through input to stdout for allowed file', () => {
const input = { tool_input: { file_path: 'README.md' } };
const { stdout } = runScript(input);
assert.strictEqual(stdout, JSON.stringify(input));
}) ? passed++ : failed++);
(test('passes through input to stdout for warned file', () => {
const input = { tool_input: { file_path: 'TODO.md' } };
const { stdout } = runScript(input);
assert.strictEqual(stdout, JSON.stringify(input));
}) ? passed++ : failed++);
(test('passes through input to stdout for empty input', () => {
const input = {};
const { stdout } = runScript(input);
assert.strictEqual(stdout, JSON.stringify(input));
}) ? passed++ : failed++);
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
runTests();