mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-01 22:53:27 +08:00
Merge origin/main into feat/insaits-security-hook
This commit is contained in:
@@ -1927,7 +1927,7 @@ function runTests() {
|
||||
PreToolUse: [{
|
||||
matcher: 'Write',
|
||||
hooks: [{
|
||||
type: 'intercept',
|
||||
type: 'command',
|
||||
command: 'echo test',
|
||||
async: 'yes' // Should be boolean, not string
|
||||
}]
|
||||
@@ -1947,7 +1947,7 @@ function runTests() {
|
||||
PostToolUse: [{
|
||||
matcher: 'Edit',
|
||||
hooks: [{
|
||||
type: 'intercept',
|
||||
type: 'command',
|
||||
command: 'echo test',
|
||||
timeout: -5 // Must be non-negative
|
||||
}]
|
||||
@@ -2105,6 +2105,31 @@ function runTests() {
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nRound 82b: validate-hooks (current official events and hook types):');
|
||||
|
||||
if (test('accepts UserPromptSubmit with omitted matcher and prompt/http/agent hooks', () => {
|
||||
const testDir = createTestDir();
|
||||
const hooksJson = JSON.stringify({
|
||||
hooks: {
|
||||
UserPromptSubmit: [
|
||||
{
|
||||
hooks: [
|
||||
{ type: 'prompt', prompt: 'Summarize the request.' },
|
||||
{ type: 'agent', prompt: 'Review for security issues.', model: 'gpt-5.4' },
|
||||
{ type: 'http', url: 'https://example.com/hooks', headers: { Authorization: 'Bearer token' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
const hooksFile = path.join(testDir, 'hooks.json');
|
||||
fs.writeFileSync(hooksFile, hooksJson);
|
||||
|
||||
const result = runValidatorWithDir('validate-hooks', 'HOOKS_FILE', hooksFile);
|
||||
assert.strictEqual(result.code, 0, 'Should accept current official hook event/type combinations');
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ── Round 83: validate-agents whitespace-only field, validate-skills empty SKILL.md ──
|
||||
|
||||
console.log('\nRound 83: validate-agents (whitespace-only frontmatter field value):');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
246
tests/lib/resolve-formatter.test.js
Normal file
246
tests/lib/resolve-formatter.test.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Tests for scripts/lib/resolve-formatter.js
|
||||
*
|
||||
* Run with: node tests/lib/resolve-formatter.test.js
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
||||
const { findProjectRoot, detectFormatter, resolveFormatterBin, clearCaches } = require('../../scripts/lib/resolve-formatter');
|
||||
|
||||
/**
|
||||
* Run a single test case, printing pass/fail.
|
||||
*
|
||||
* @param {string} name - Test description
|
||||
* @param {() => void} fn - Test body (throws on failure)
|
||||
* @returns {boolean} Whether the test passed
|
||||
*/
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Track all created tmp dirs for cleanup */
|
||||
const tmpDirs = [];
|
||||
|
||||
/**
|
||||
* Create a temporary directory and track it for cleanup.
|
||||
*
|
||||
* @returns {string} Absolute path to the new temp directory
|
||||
*/
|
||||
function makeTmpDir() {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolve-fmt-'));
|
||||
tmpDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all tracked temporary directories.
|
||||
*/
|
||||
function cleanupTmpDirs() {
|
||||
for (const dir of tmpDirs) {
|
||||
try {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
}
|
||||
tmpDirs.length = 0;
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
console.log('\n=== Testing resolve-formatter.js ===\n');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function run(name, fn) {
|
||||
clearCaches();
|
||||
if (test(name, fn)) passed++;
|
||||
else failed++;
|
||||
}
|
||||
|
||||
// ── findProjectRoot ───────────────────────────────────────────
|
||||
|
||||
run('findProjectRoot: finds package.json in parent dir', () => {
|
||||
const root = makeTmpDir();
|
||||
const sub = path.join(root, 'src', 'lib');
|
||||
fs.mkdirSync(sub, { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'package.json'), '{}');
|
||||
|
||||
assert.strictEqual(findProjectRoot(sub), root);
|
||||
});
|
||||
|
||||
run('findProjectRoot: returns startDir when no package.json', () => {
|
||||
const root = makeTmpDir();
|
||||
const sub = path.join(root, 'deep');
|
||||
fs.mkdirSync(sub, { recursive: true });
|
||||
|
||||
// No package.json anywhere in tmp → falls back to startDir
|
||||
assert.strictEqual(findProjectRoot(sub), sub);
|
||||
});
|
||||
|
||||
run('findProjectRoot: caches result for same startDir', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'package.json'), '{}');
|
||||
|
||||
const first = findProjectRoot(root);
|
||||
// Remove package.json — cache should still return the old result
|
||||
fs.unlinkSync(path.join(root, 'package.json'));
|
||||
const second = findProjectRoot(root);
|
||||
|
||||
assert.strictEqual(first, second);
|
||||
});
|
||||
|
||||
// ── detectFormatter ───────────────────────────────────────────
|
||||
|
||||
run('detectFormatter: detects biome.json', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'biome.json'), '{}');
|
||||
assert.strictEqual(detectFormatter(root), 'biome');
|
||||
});
|
||||
|
||||
run('detectFormatter: detects biome.jsonc', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'biome.jsonc'), '{}');
|
||||
assert.strictEqual(detectFormatter(root), 'biome');
|
||||
});
|
||||
|
||||
run('detectFormatter: detects .prettierrc', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, '.prettierrc'), '{}');
|
||||
assert.strictEqual(detectFormatter(root), 'prettier');
|
||||
});
|
||||
|
||||
run('detectFormatter: detects prettier.config.js', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'prettier.config.js'), 'module.exports = {}');
|
||||
assert.strictEqual(detectFormatter(root), 'prettier');
|
||||
});
|
||||
|
||||
run('detectFormatter: detects prettier key in package.json', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'test', prettier: { singleQuote: true } }));
|
||||
assert.strictEqual(detectFormatter(root), 'prettier');
|
||||
});
|
||||
|
||||
run('detectFormatter: ignores package.json without prettier key', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'test' }));
|
||||
assert.strictEqual(detectFormatter(root), null);
|
||||
});
|
||||
|
||||
run('detectFormatter: biome takes priority over prettier', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'biome.json'), '{}');
|
||||
fs.writeFileSync(path.join(root, '.prettierrc'), '{}');
|
||||
assert.strictEqual(detectFormatter(root), 'biome');
|
||||
});
|
||||
|
||||
run('detectFormatter: returns null when no config found', () => {
|
||||
const root = makeTmpDir();
|
||||
assert.strictEqual(detectFormatter(root), null);
|
||||
});
|
||||
|
||||
// ── resolveFormatterBin ───────────────────────────────────────
|
||||
|
||||
run('resolveFormatterBin: uses local biome binary when available', () => {
|
||||
const root = makeTmpDir();
|
||||
const binDir = path.join(root, 'node_modules', '.bin');
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
const binName = process.platform === 'win32' ? 'biome.cmd' : 'biome';
|
||||
fs.writeFileSync(path.join(binDir, binName), '');
|
||||
|
||||
const result = resolveFormatterBin(root, 'biome');
|
||||
assert.strictEqual(result.bin, path.join(binDir, binName));
|
||||
assert.deepStrictEqual(result.prefix, []);
|
||||
});
|
||||
|
||||
run('resolveFormatterBin: falls back to npx for biome', () => {
|
||||
const root = makeTmpDir();
|
||||
const result = resolveFormatterBin(root, 'biome');
|
||||
const expectedBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
assert.strictEqual(result.bin, expectedBin);
|
||||
assert.deepStrictEqual(result.prefix, ['@biomejs/biome']);
|
||||
});
|
||||
|
||||
run('resolveFormatterBin: uses local prettier binary when available', () => {
|
||||
const root = makeTmpDir();
|
||||
const binDir = path.join(root, 'node_modules', '.bin');
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
const binName = process.platform === 'win32' ? 'prettier.cmd' : 'prettier';
|
||||
fs.writeFileSync(path.join(binDir, binName), '');
|
||||
|
||||
const result = resolveFormatterBin(root, 'prettier');
|
||||
assert.strictEqual(result.bin, path.join(binDir, binName));
|
||||
assert.deepStrictEqual(result.prefix, []);
|
||||
});
|
||||
|
||||
run('resolveFormatterBin: falls back to npx for prettier', () => {
|
||||
const root = makeTmpDir();
|
||||
const result = resolveFormatterBin(root, 'prettier');
|
||||
const expectedBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
assert.strictEqual(result.bin, expectedBin);
|
||||
assert.deepStrictEqual(result.prefix, ['prettier']);
|
||||
});
|
||||
|
||||
run('resolveFormatterBin: returns null for unknown formatter', () => {
|
||||
const root = makeTmpDir();
|
||||
const result = resolveFormatterBin(root, 'unknown');
|
||||
assert.strictEqual(result, null);
|
||||
});
|
||||
|
||||
run('resolveFormatterBin: caches resolved binary', () => {
|
||||
const root = makeTmpDir();
|
||||
const binDir = path.join(root, 'node_modules', '.bin');
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
const binName = process.platform === 'win32' ? 'biome.cmd' : 'biome';
|
||||
fs.writeFileSync(path.join(binDir, binName), '');
|
||||
|
||||
const first = resolveFormatterBin(root, 'biome');
|
||||
fs.unlinkSync(path.join(binDir, binName));
|
||||
const second = resolveFormatterBin(root, 'biome');
|
||||
|
||||
assert.strictEqual(first.bin, second.bin);
|
||||
});
|
||||
|
||||
// ── clearCaches ───────────────────────────────────────────────
|
||||
|
||||
run('clearCaches: clears all cached values', () => {
|
||||
const root = makeTmpDir();
|
||||
fs.writeFileSync(path.join(root, 'package.json'), '{}');
|
||||
fs.writeFileSync(path.join(root, 'biome.json'), '{}');
|
||||
|
||||
findProjectRoot(root);
|
||||
detectFormatter(root);
|
||||
resolveFormatterBin(root, 'biome');
|
||||
|
||||
clearCaches();
|
||||
|
||||
// After clearing, removing config should change detection
|
||||
fs.unlinkSync(path.join(root, 'biome.json'));
|
||||
assert.strictEqual(detectFormatter(root), null);
|
||||
});
|
||||
|
||||
// ── Summary & Cleanup ─────────────────────────────────────────
|
||||
|
||||
cleanupTmpDirs();
|
||||
|
||||
console.log('\n=== Test Results ===');
|
||||
console.log(`Passed: ${passed}`);
|
||||
console.log(`Failed: ${failed}`);
|
||||
console.log(`Total: ${passed + failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
runTests();
|
||||
@@ -10,24 +10,28 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const testsDir = __dirname;
|
||||
const testFiles = [
|
||||
'lib/utils.test.js',
|
||||
'lib/package-manager.test.js',
|
||||
'lib/session-manager.test.js',
|
||||
'lib/session-aliases.test.js',
|
||||
'lib/project-detect.test.js',
|
||||
'hooks/hooks.test.js',
|
||||
'hooks/evaluate-session.test.js',
|
||||
'hooks/suggest-compact.test.js',
|
||||
'integration/hooks.test.js',
|
||||
'ci/validators.test.js',
|
||||
'scripts/claw.test.js',
|
||||
'scripts/setup-package-manager.test.js',
|
||||
'scripts/skill-create-output.test.js'
|
||||
];
|
||||
|
||||
/**
|
||||
* Discover all *.test.js files under testsDir (relative paths for stable output order).
|
||||
*/
|
||||
function discoverTestFiles(dir, baseDir = dir, acc = []) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const e of entries) {
|
||||
const full = path.join(dir, e.name);
|
||||
const rel = path.relative(baseDir, full);
|
||||
if (e.isDirectory()) {
|
||||
discoverTestFiles(full, baseDir, acc);
|
||||
} else if (e.isFile() && e.name.endsWith('.test.js')) {
|
||||
acc.push(rel);
|
||||
}
|
||||
}
|
||||
return acc.sort();
|
||||
}
|
||||
|
||||
const testFiles = discoverTestFiles(testsDir);
|
||||
|
||||
const BOX_W = 58; // inner width between ║ delimiters
|
||||
const boxLine = (s) => `║${s.padEnd(BOX_W)}║`;
|
||||
const boxLine = s => `║${s.padEnd(BOX_W)}║`;
|
||||
|
||||
console.log('╔' + '═'.repeat(BOX_W) + '╗');
|
||||
console.log(boxLine(' Everything Claude Code - Test Suite'));
|
||||
@@ -67,6 +71,17 @@ for (const testFile of testFiles) {
|
||||
|
||||
if (passedMatch) totalPassed += parseInt(passedMatch[1], 10);
|
||||
if (failedMatch) totalFailed += parseInt(failedMatch[1], 10);
|
||||
|
||||
if (result.error) {
|
||||
console.log(`✗ ${testFile} failed to start: ${result.error.message}`);
|
||||
totalFailed += failedMatch ? 0 : 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.log(`✗ ${testFile} exited with status ${result.status}`);
|
||||
totalFailed += failedMatch ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
totalTests = totalPassed + totalFailed;
|
||||
|
||||
Reference in New Issue
Block a user