mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-30 16:45:48 +08:00
255 lines
7.2 KiB
JavaScript
255 lines
7.2 KiB
JavaScript
/**
|
|
* Direct subprocess tests for scripts/hooks/plugin-hook-bootstrap.js.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'hooks', 'plugin-hook-bootstrap.js');
|
|
|
|
function createTempDir() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'plugin-hook-bootstrap-'));
|
|
}
|
|
|
|
function cleanup(dirPath) {
|
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
}
|
|
|
|
function writeFile(root, relativePath, content) {
|
|
const filePath = path.join(root, relativePath);
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
return filePath;
|
|
}
|
|
|
|
function run(args = [], options = {}) {
|
|
return spawnSync(process.execPath, [SCRIPT, ...args], {
|
|
input: options.input || '',
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
CLAUDE_PLUGIN_ROOT: options.root || '',
|
|
ECC_PLUGIN_ROOT: options.eccRoot || '',
|
|
...(options.env || {}),
|
|
},
|
|
cwd: options.cwd || process.cwd(),
|
|
timeout: 10000,
|
|
});
|
|
}
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` PASS ${name}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` FAIL ${name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function runTests() {
|
|
console.log('\n=== Testing plugin-hook-bootstrap.js ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
if (test('passes stdin through when required bootstrap inputs are missing', () => {
|
|
const result = run([], { input: '{"ok":true}' });
|
|
|
|
assert.strictEqual(result.status, 0);
|
|
assert.strictEqual(result.stdout, '{"ok":true}');
|
|
assert.strictEqual(result.stderr, '');
|
|
})) passed++; else failed++;
|
|
|
|
if (test('node mode runs target script with plugin root environment', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
writeFile(root, path.join('scripts', 'hook.js'), `
|
|
const fs = require('fs');
|
|
const raw = fs.readFileSync(0, 'utf8');
|
|
process.stdout.write(JSON.stringify({
|
|
raw,
|
|
args: process.argv.slice(2),
|
|
claudeRoot: process.env.CLAUDE_PLUGIN_ROOT,
|
|
eccRoot: process.env.ECC_PLUGIN_ROOT,
|
|
}));
|
|
`);
|
|
|
|
const result = run(['node', path.join('scripts', 'hook.js'), 'one', 'two'], {
|
|
root,
|
|
input: 'payload',
|
|
});
|
|
const parsed = JSON.parse(result.stdout);
|
|
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
assert.strictEqual(parsed.raw, 'payload');
|
|
assert.deepStrictEqual(parsed.args, ['one', 'two']);
|
|
assert.strictEqual(parsed.claudeRoot, root);
|
|
assert.strictEqual(parsed.eccRoot, root);
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('node mode passes original stdin when child exits cleanly without stdout', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
writeFile(root, path.join('scripts', 'silent.js'), 'process.exit(0);\n');
|
|
|
|
const result = run(['node', path.join('scripts', 'silent.js')], {
|
|
root,
|
|
input: 'raw-input',
|
|
});
|
|
|
|
assert.strictEqual(result.status, 0);
|
|
assert.strictEqual(result.stdout, 'raw-input');
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('node mode forwards child stdout and exit status for blocking hooks', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
writeFile(root, path.join('scripts', 'block.js'), `
|
|
process.stdout.write('blocked output');
|
|
process.stderr.write('blocked stderr\\n');
|
|
process.exit(2);
|
|
`);
|
|
|
|
const result = run(['node', path.join('scripts', 'block.js')], {
|
|
root,
|
|
input: 'raw-input',
|
|
});
|
|
|
|
assert.strictEqual(result.status, 2);
|
|
assert.strictEqual(result.stdout, 'blocked output');
|
|
assert.strictEqual(result.stderr, 'blocked stderr\n');
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('node mode leaves stdout empty for nonzero child without stdout', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
writeFile(root, path.join('scripts', 'fail.js'), `
|
|
process.stderr.write('failure stderr\\n');
|
|
process.exit(7);
|
|
`);
|
|
|
|
const result = run(['node', path.join('scripts', 'fail.js')], {
|
|
root,
|
|
input: 'raw-input',
|
|
});
|
|
|
|
assert.strictEqual(result.status, 7);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.stderr, 'failure stderr\n');
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('shell mode runs target script through an available shell', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
writeFile(root, path.join('scripts', 'hook.sh'), [
|
|
'input=$(cat)',
|
|
'printf "shell:%s:%s" "$1" "$input"',
|
|
'',
|
|
].join('\n'));
|
|
|
|
const result = run(['shell', path.join('scripts', 'hook.sh'), 'arg'], {
|
|
root,
|
|
input: 'payload',
|
|
env: fs.existsSync('/bin/sh') ? { BASH: '/bin/sh' } : {},
|
|
});
|
|
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
assert.strictEqual(result.stdout, 'shell:arg:payload');
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('shell mode fails open when no shell runtime is available', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
writeFile(root, path.join('scripts', 'hook.sh'), 'printf unreachable\n');
|
|
|
|
const result = run(['shell', path.join('scripts', 'hook.sh')], {
|
|
root,
|
|
input: 'raw-input',
|
|
env: { PATH: '', BASH: '' },
|
|
});
|
|
|
|
assert.strictEqual(result.status, 0);
|
|
assert.strictEqual(result.stdout, 'raw-input');
|
|
assert.ok(result.stderr.includes('shell runtime unavailable'));
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('rejects target paths that escape the plugin root', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
const result = run(['node', path.join('..', 'outside.js')], {
|
|
root,
|
|
input: 'raw-input',
|
|
});
|
|
|
|
assert.strictEqual(result.status, 0);
|
|
assert.strictEqual(result.stdout, 'raw-input');
|
|
assert.ok(result.stderr.includes('Path traversal rejected'));
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('unknown mode fails open with stderr warning', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
const result = run(['python', 'hook.py'], {
|
|
root,
|
|
input: 'raw-input',
|
|
});
|
|
|
|
assert.strictEqual(result.status, 0);
|
|
assert.strictEqual(result.stdout, 'raw-input');
|
|
assert.ok(result.stderr.includes('unknown bootstrap mode: python'));
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('missing node target returns child failure diagnostics', () => {
|
|
const root = createTempDir();
|
|
try {
|
|
const result = run(['node', path.join('scripts', 'missing.js')], {
|
|
root,
|
|
input: 'raw-input',
|
|
});
|
|
|
|
assert.strictEqual(result.status, 1);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.ok(result.stderr.includes('Cannot find module'));
|
|
} finally {
|
|
cleanup(root);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|