Files
ECC/tests/lib/codex-plugin-setup.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

631 lines
22 KiB
JavaScript

'use strict';
const assert = require('assert');
const {
CodexPluginSetupError,
OFFICIAL_MARKETPLACE_REPO,
executeFile,
normalizeGitHubGitOrigin,
parseMarketplaceInventory,
parseMarketplaceUpgradeResult,
parsePluginInventory,
reconcileCodexPlugin,
resolveMarketplaceRepository,
} = require('../../scripts/lib/codex-plugin-setup');
const MARKETPLACE_LIST = ['plugin', 'marketplace', 'list', '--json'];
const PLUGIN_LIST = ['plugin', 'list', '--json'];
const MARKETPLACE_ADD = [
'plugin', 'marketplace', 'add', OFFICIAL_MARKETPLACE_REPO, '--json',
];
const MARKETPLACE_UPGRADE = [
'plugin', 'marketplace', 'upgrade', 'ecc', '--json',
];
const PLUGIN_ADD = ['plugin', 'add', 'ecc@ecc', '--json'];
function marketplaceInventory(installed = false) {
return JSON.stringify({
marketplaces: installed ? [{ name: 'ecc', root: '/cache/ecc' }] : [],
});
}
function pluginInventory(installed = false, overrides = {}) {
const ecc = {
pluginId: 'ecc@ecc',
name: 'ecc',
marketplaceName: 'ecc',
version: '2.0.0',
installed: true,
enabled: true,
...overrides,
};
return JSON.stringify({
installed: installed ? [ecc] : [],
available: [],
});
}
function marketplaceUpgradeResult(overrides = {}) {
return JSON.stringify({
selectedMarketplaces: ['ecc'],
upgradedRoots: ['/cache/ecc'],
errors: [],
...overrides,
});
}
function createExecFile(steps) {
const calls = [];
const execFile = (command, args, options, callback) => {
calls.push({ command, args: [...args], options: { ...options } });
const step = steps[calls.length - 1];
if (!step) {
callback(new Error(`Unexpected Codex invocation: ${args.join(' ')}`));
return;
}
if (step.command) assert.strictEqual(command, step.command);
assert.deepStrictEqual(args, step.args);
callback(step.error || null, step.stdout || '', step.stderr || '');
};
return { calls, execFile };
}
function dependenciesFor(fake, overrides = {}) {
return {
execFile: fake.execFile,
resolveMarketplaceRepository: async () => 'https://github.com/affaan-m/ECC.git',
...overrides,
};
}
async function expectSetupError(promise, code, messagePattern) {
await assert.rejects(promise, error => {
assert.ok(error instanceof CodexPluginSetupError);
assert.strictEqual(error.code, code);
assert.match(error.message, messagePattern);
return true;
});
}
async function test(name, fn) {
try {
await fn();
console.log(` \u2713 ${name}`);
return true;
} catch (error) {
console.log(` \u2717 ${name}`);
console.log(` Error: ${error.stack || error.message}`);
return false;
}
}
async function runTests() {
console.log('\n=== Codex native plugin setup library tests ===\n');
let passed = 0;
let failed = 0;
const cases = [
['parses current Codex marketplace and plugin JSON inventory shapes', () => {
assert.deepStrictEqual(
parseMarketplaceInventory(marketplaceInventory(true)),
[{ name: 'ecc', root: '/cache/ecc' }]
);
assert.strictEqual(
parsePluginInventory(pluginInventory(true)).installed[0].pluginId,
'ecc@ecc'
);
assert.strictEqual(
normalizeGitHubGitOrigin('git@github.com:affaan-m/ECC.git'),
'affaan-m/ecc'
);
}],
['resolves marketplace provenance with execFile and exact Git argv', async () => {
const fake = createExecFile([{
command: 'git',
args: ['-C', '/cache/ecc', 'remote', 'get-url', 'origin'],
stdout: 'https://github.com/affaan-m/ECC.git\n',
}]);
const repository = await resolveMarketplaceRepository(
{ name: 'ecc', root: '/cache/ecc' },
{ cwd: '/workspace with spaces' },
{ execFile: fake.execFile }
);
assert.strictEqual(repository, 'https://github.com/affaan-m/ECC.git');
assert.strictEqual(fake.calls[0].options.shell, false);
assert.strictEqual(fake.calls[0].options.cwd, '/workspace with spaces');
assert.ok(fake.calls[0].options.timeout > 0);
assert.strictEqual(fake.calls[0].options.killSignal, 'SIGKILL');
}],
['preserves the original execFile error and attaches callback output', async () => {
const original = new Error('provider failed');
const execFile = (command, args, options, callback) => {
callback(original, 'partial stdout', 'partial stderr');
};
await assert.rejects(
executeFile(execFile, 'codex', ['plugin', 'list'], {}),
error => {
assert.strictEqual(error, original);
assert.strictEqual(error.stdout, 'partial stdout');
assert.strictEqual(error.stderr, 'partial stderr');
assert.match(error.stack, /provider failed/);
return true;
}
);
}],
['maps provider and provenance timeouts to distinct structured errors', async () => {
const providerTimeout = Object.assign(new Error('timed out'), {
code: 'ETIMEDOUT',
killed: true,
signal: 'SIGKILL',
});
const provider = createExecFile([{ args: MARKETPLACE_LIST, error: providerTimeout }]);
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(provider)),
'CODEX_COMMAND_TIMEOUT',
/timed out/i
);
const provenanceTimeout = Object.assign(new Error('timed out'), {
code: 'ETIMEDOUT',
killed: true,
signal: 'SIGKILL',
});
const provenance = createExecFile([{
command: 'git',
args: ['-C', '/cache/ecc', 'remote', 'get-url', 'origin'],
error: provenanceTimeout,
}]);
await expectSetupError(
resolveMarketplaceRepository(
{ name: 'ecc', root: '/cache/ecc' },
{},
{ execFile: provenance.execFile }
),
'MARKETPLACE_PROVENANCE_TIMEOUT',
/timed out/i
);
assert.ok(provenance.calls[0].options.timeout > 0);
assert.strictEqual(provenance.calls[0].options.killSignal, 'SIGKILL');
}],
['rejects unverifiable Git provenance without using a shell', async () => {
const gitFailure = Object.assign(new Error('no origin'), {
stderr: 'fatal: No such remote origin',
});
const fake = createExecFile([{
command: 'git',
args: ['-C', '/cache/ecc', 'remote', 'get-url', 'origin'],
error: gitFailure,
}]);
await expectSetupError(
resolveMarketplaceRepository(
{ name: 'ecc', root: '/cache/ecc' },
{},
{ execFile: fake.execFile }
),
'MARKETPLACE_COLLISION',
/provenance could not be verified/i
);
assert.strictEqual(fake.calls[0].options.shell, false);
}],
['fresh install uses exact native Codex argv and verifies both mutations', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(false) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
{ args: MARKETPLACE_ADD, stdout: '{"alreadyAdded":false}' },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_ADD, stdout: '{"pluginId":"ecc@ecc"}' },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
]);
const result = await reconcileCodexPlugin(
{ cwd: '/workspace with spaces' },
dependenciesFor(fake)
);
assert.deepStrictEqual(result, {
action: 'installed',
marketplaceAction: 'added',
pluginId: 'ecc@ecc',
restartRequired: true,
});
assert.deepStrictEqual(fake.calls.map(call => call.args), [
MARKETPLACE_LIST,
PLUGIN_LIST,
MARKETPLACE_ADD,
MARKETPLACE_LIST,
PLUGIN_ADD,
PLUGIN_LIST,
]);
for (const call of fake.calls) {
assert.strictEqual(call.command, 'codex');
assert.strictEqual(call.options.cwd, '/workspace with spaces');
assert.strictEqual(call.options.shell, false);
assert.ok(call.options.timeout > 0);
assert.strictEqual(call.options.killSignal, 'SIGKILL');
assert.ok(!call.args.includes('--config'));
assert.ok(!call.args.includes('-c'));
}
}],
['already installed and enabled is refreshed and strongly verified', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
{ args: MARKETPLACE_UPGRADE, stdout: marketplaceUpgradeResult() },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
]);
const result = await reconcileCodexPlugin({}, dependenciesFor(fake));
assert.deepStrictEqual(result, {
action: 'updated',
marketplaceAction: 'upgraded',
pluginId: 'ecc@ecc',
restartRequired: true,
});
assert.deepStrictEqual(fake.calls.map(call => call.args), [
MARKETPLACE_LIST,
PLUGIN_LIST,
MARKETPLACE_UPGRADE,
MARKETPLACE_LIST,
PLUGIN_LIST,
]);
}],
['fails closed when native refresh does not confirm the marketplace root', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
{
args: MARKETPLACE_UPGRADE,
stdout: marketplaceUpgradeResult({ upgradedRoots: [] }),
},
]);
await assert.rejects(
reconcileCodexPlugin({}, dependenciesFor(fake)),
error => {
assert.strictEqual(error.code, 'MARKETPLACE_REFRESH_FAILED');
assert.strictEqual(error.phase, 'marketplace-upgrade');
assert.deepStrictEqual(error.argv, MARKETPLACE_UPGRADE);
assert.match(error.message, /did not confirm.*refreshed/i);
return true;
}
);
assert.strictEqual(fake.calls.length, 3);
}],
['accepts the provider root across Windows separator and case differences', () => {
const result = parseMarketplaceUpgradeResult(
marketplaceUpgradeResult({
upgradedRoots: ['c:/users/hira/.codex/marketplaces/ecc'],
}),
{ name: 'ecc', root: 'C:\\Users\\Hira\\.codex\\marketplaces\\ecc' }
);
assert.deepStrictEqual(result.selectedMarketplaces, ['ecc']);
}],
['rejects ambiguous native refresh results for a targeted upgrade', () => {
assert.throws(
() => parseMarketplaceUpgradeResult(
marketplaceUpgradeResult({
selectedMarketplaces: ['ecc', 'other'],
upgradedRoots: ['/cache/ecc', '/cache/other'],
}),
{ name: 'ecc', root: '/cache/ecc' }
),
error => (
error.code === 'MARKETPLACE_REFRESH_FAILED'
&& error.phase === 'marketplace-upgrade'
)
);
assert.throws(
() => parseMarketplaceUpgradeResult(
marketplaceUpgradeResult({ errors: [{ message: 'dirty checkout' }] }),
{ name: 'ecc', root: '/cache/ecc' }
),
error => error.code === 'MARKETPLACE_REFRESH_FAILED'
);
}],
['fails closed when post-refresh marketplace provenance changes', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
{ args: MARKETPLACE_UPGRADE, stdout: marketplaceUpgradeResult() },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
]);
let provenanceChecks = 0;
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(fake, {
resolveMarketplaceRepository: async () => {
provenanceChecks += 1;
return provenanceChecks === 1
? 'https://github.com/affaan-m/ECC.git'
: 'https://github.com/attacker/ecc.git';
},
})),
'MARKETPLACE_COLLISION',
/not the official/i
);
assert.strictEqual(provenanceChecks, 2);
assert.strictEqual(fake.calls.length, 4);
}],
['fails closed on malformed post-refresh plugin inventory', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
{ args: MARKETPLACE_UPGRADE, stdout: marketplaceUpgradeResult() },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: '{not json' },
]);
await assert.rejects(
reconcileCodexPlugin({}, dependenciesFor(fake)),
error => {
assert.strictEqual(error.code, 'INVALID_PLUGIN_INVENTORY');
assert.strictEqual(error.phase, 'plugin-verification');
return true;
}
);
assert.strictEqual(fake.calls.length, 5);
}],
['dry-run fresh install is inventory-only planning', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(false) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
]);
const result = await reconcileCodexPlugin(
{ dryRun: true },
dependenciesFor(fake)
);
assert.deepStrictEqual(result, {
action: 'would-install',
dryRun: true,
marketplaceAction: 'would-add',
pluginId: 'ecc@ecc',
restartRequired: true,
});
assert.deepStrictEqual(fake.calls.map(call => call.args), [
MARKETPLACE_LIST,
PLUGIN_LIST,
]);
}],
['dry-run repair is inventory-only update planning', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(false) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true, { enabled: false }) },
]);
const result = await reconcileCodexPlugin(
{ dryRun: true },
dependenciesFor(fake)
);
assert.deepStrictEqual(result, {
action: 'would-update',
dryRun: true,
marketplaceAction: 'would-add',
pluginId: 'ecc@ecc',
restartRequired: true,
});
assert.strictEqual(fake.calls.length, 2);
}],
['dry-run keeps reconciled state unchanged without mutation', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
]);
const result = await reconcileCodexPlugin(
{ dryRun: true },
dependenciesFor(fake)
);
assert.deepStrictEqual(result, {
action: 'unchanged',
dryRun: true,
marketplaceAction: 'would-upgrade',
pluginId: 'ecc@ecc',
restartRequired: false,
});
assert.strictEqual(fake.calls.length, 2);
}],
['upgrades an existing marketplace before installing a missing plugin', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
{ args: MARKETPLACE_UPGRADE, stdout: marketplaceUpgradeResult() },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
{ args: PLUGIN_ADD, stdout: '{"pluginId":"ecc@ecc"}' },
{ args: PLUGIN_LIST, stdout: pluginInventory(true) },
]);
const result = await reconcileCodexPlugin({}, dependenciesFor(fake));
assert.strictEqual(result.action, 'installed');
assert.strictEqual(result.marketplaceAction, 'upgraded');
assert.deepStrictEqual(fake.calls.map(call => call.args), [
MARKETPLACE_LIST,
PLUGIN_LIST,
MARKETPLACE_UPGRADE,
MARKETPLACE_LIST,
PLUGIN_LIST,
PLUGIN_ADD,
PLUGIN_LIST,
]);
}],
['fails closed when the ecc marketplace has untrusted provenance', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
]);
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(fake, {
resolveMarketplaceRepository: async marketplace => {
assert.strictEqual(marketplace.root, '/cache/ecc');
return 'https://github.com/attacker/ecc.git';
},
})),
'MARKETPLACE_COLLISION',
/refusing.*ecc.*marketplace/i
);
assert.deepStrictEqual(fake.calls.map(call => call.args), [
MARKETPLACE_LIST,
PLUGIN_LIST,
]);
}],
['rejects relative and insecure Git origins before marketplace mutation', async () => {
for (const origin of [
'affaan-m/ecc',
'http://github.com/affaan-m/ECC.git',
]) {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
]);
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(fake, {
resolveMarketplaceRepository: async () => origin,
})),
'MARKETPLACE_COLLISION',
/not the official/i
);
assert.deepStrictEqual(fake.calls.map(call => call.args), [
MARKETPLACE_LIST,
PLUGIN_LIST,
]);
}
}],
['reports a missing Codex CLI without attempting another command', async () => {
const missing = Object.assign(new Error('spawn codex ENOENT'), { code: 'ENOENT' });
const fake = createExecFile([{ args: MARKETPLACE_LIST, error: missing }]);
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(fake)),
'CODEX_NOT_FOUND',
/not installed|not on path/i
);
assert.strictEqual(fake.calls.length, 1);
}],
['rejects malformed marketplace and plugin JSON inventories', async () => {
assert.throws(
() => parseMarketplaceInventory('{not json'),
error => error.code === 'INVALID_MARKETPLACE_INVENTORY'
);
assert.throws(
() => parsePluginInventory('{"installed":{}}'),
error => error.code === 'INVALID_PLUGIN_INVENTORY'
);
assert.throws(
() => parseMarketplaceInventory(JSON.stringify({
marketplaces: [
{ name: 'ecc', root: '/one' },
{ name: 'ecc', root: '/two' },
],
})),
error => error.code === 'INVALID_MARKETPLACE_INVENTORY'
);
assert.throws(
() => parseMarketplaceInventory('{"marketplaces":[{"name":"ecc","root":""}]}'),
error => error.code === 'INVALID_MARKETPLACE_INVENTORY'
);
assert.throws(
() => parsePluginInventory('{"installed":[],"available":[{}]}'),
error => error.code === 'INVALID_PLUGIN_INVENTORY'
);
assert.throws(
() => parsePluginInventory(JSON.stringify({
installed: [
{ pluginId: 'ecc@ecc', installed: true, enabled: true },
{ pluginId: 'ecc@ecc', installed: true, enabled: true },
],
available: [],
})),
error => error.code === 'INVALID_PLUGIN_INVENTORY'
);
assert.strictEqual(normalizeGitHubGitOrigin(null), null);
assert.strictEqual(normalizeGitHubGitOrigin('not a repository'), null);
assert.strictEqual(normalizeGitHubGitOrigin('affaan-m/ECC'), null);
assert.strictEqual(
normalizeGitHubGitOrigin('http://github.com/affaan-m/ECC.git'),
null
);
}],
['surfaces mutation command failures with phase and exact argv', async () => {
const commandFailure = Object.assign(new Error('upgrade failed'), {
code: 1,
stderr: 'network unavailable',
});
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
{ args: MARKETPLACE_UPGRADE, error: commandFailure },
]);
await assert.rejects(
reconcileCodexPlugin({}, dependenciesFor(fake)),
error => {
assert.strictEqual(error.code, 'CODEX_COMMAND_FAILED');
assert.strictEqual(error.phase, 'marketplace-upgrade');
assert.deepStrictEqual(error.argv, MARKETPLACE_UPGRADE);
assert.match(error.message, /network unavailable/);
return true;
}
);
}],
['fails when post-install verification does not find enabled ECC', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(false) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
{ args: MARKETPLACE_ADD, stdout: '{"alreadyAdded":false}' },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(true) },
{ args: PLUGIN_ADD, stdout: '{"pluginId":"ecc@ecc"}' },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
]);
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(fake)),
'PLUGIN_VERIFICATION_FAILED',
/verify.*ecc@ecc/i
);
}],
['fails when marketplace verification cannot observe ECC', async () => {
const fake = createExecFile([
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(false) },
{ args: PLUGIN_LIST, stdout: pluginInventory(false) },
{ args: MARKETPLACE_ADD, stdout: '{"alreadyAdded":false}' },
{ args: MARKETPLACE_LIST, stdout: marketplaceInventory(false) },
]);
await expectSetupError(
reconcileCodexPlugin({}, dependenciesFor(fake)),
'MARKETPLACE_VERIFICATION_FAILED',
/verify.*ecc marketplace/i
);
assert.strictEqual(fake.calls.length, 4);
}],
];
for (const [name, fn] of cases) {
if (await test(name, fn)) passed += 1;
else failed += 1;
}
console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);
if (failed > 0) process.exit(1);
}
runTests().catch(error => {
console.error(error);
process.exit(1);
});