Files
ECC/tests/codex-native-hooks.test.js
T
28e53a0bc1 feat(install): add guided multi-harness installer (#2649)
* feat(install): add guided Claude plugin setup

* fix: support Claude command shims on Windows

* feat: support safe Claude plugin scope migration

* fix(install): preserve interactive setup terminal

* fix(install): auto-migrate setup scope changes

* feat(install): add guided multi-harness installer

* fix(install): sync Yarn binary metadata

* fix(install): handle wizard EOF on Node 18

* ci: allow installer matrix tests to finish

* test(install): allow slower PowerShell delegation

* fix(install): harden guided provider reconciliation

* test(install): harden packaged and local compatibility

* chore: prepare guided installer release 2.2.0

* fix(install): report refreshed Codex marketplace state

* fix(install): verify managed content provenance

* test(install): allow empty Yarn smoke fixture

* test(install): invoke Windows package shims safely

* fix(install): close cross-platform release gaps

* fix(install): require trusted GitHub origins

* fix(install): preserve hook profile precedence

* refactor(install): centralize trusted GitHub origins

* ci: retrigger workflow run after merge of main

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 15:39:49 -04:00

95 lines
2.7 KiB
JavaScript

/**
* Integration checks for the native Codex plugin hook boundary.
*
* Run with: node tests/codex-native-hooks.test.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 repoRoot = path.resolve(__dirname, '..');
const hookConfig = JSON.parse(fs.readFileSync(path.join(repoRoot, 'hooks', 'codex-hooks.json'), 'utf8'));
const sessionStart = hookConfig.hooks.SessionStart[0].hooks[0];
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(` ✓ ${name}`);
passed++;
} catch (error) {
console.log(` ✗ ${name}`);
console.log(` Error: ${error.message}`);
failed++;
}
}
function runSessionStart({ pluginRoot }) {
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-codex-hook-'));
const userHome = path.join(fixtureRoot, 'user-home');
const projectDir = path.join(fixtureRoot, 'project');
const pluginData = path.join(fixtureRoot, 'plugin-data');
fs.mkdirSync(userHome, { recursive: true });
fs.mkdirSync(projectDir, { recursive: true });
fs.mkdirSync(pluginData, { recursive: true });
const env = {
...process.env,
HOME: userHome,
USERPROFILE: userHome,
PLUGIN_DATA: pluginData
};
delete env.CLAUDE_PLUGIN_ROOT;
if (pluginRoot) {
env.PLUGIN_ROOT = pluginRoot;
} else {
delete env.PLUGIN_ROOT;
}
const input = JSON.stringify({
session_id: 'codex-native-hook-test',
transcript_path: path.join(fixtureRoot, 'transcript.jsonl'),
cwd: projectDir,
hook_event_name: 'SessionStart',
source: 'startup'
});
try {
return spawnSync(sessionStart.command, {
cwd: projectDir,
env,
input,
encoding: 'utf8',
shell: true,
timeout: 15_000
});
} finally {
fs.rmSync(fixtureRoot, { recursive: true, force: true });
}
}
test('installed Codex SessionStart hook resolves from PLUGIN_ROOT and emits Codex output', () => {
const result = runSessionStart({ pluginRoot: repoRoot });
assert.strictEqual(result.status, 0, result.stderr || result.error?.message);
const output = JSON.parse(result.stdout);
assert.strictEqual(output.hookSpecificOutput.hookEventName, 'SessionStart');
assert.strictEqual(typeof output.hookSpecificOutput.additionalContext, 'string');
});
test('Codex SessionStart hook fails closed when PLUGIN_ROOT is absent', () => {
const result = runSessionStart({ pluginRoot: null });
assert.notStrictEqual(result.status, 0, 'Hook must not fall through to a stale ~/.claude plugin');
assert.match(result.stderr, /Missing Codex PLUGIN_ROOT/);
});
console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);