Files
ECC/tests/lib/harness-capabilities.test.js
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

186 lines
7.4 KiB
JavaScript

/**
* Tests for scripts/lib/harness-capabilities.js
*/
const assert = require('assert');
const { SUPPORTED_INSTALL_TARGETS } = require('../../scripts/lib/install-manifests');
const { listInstallTargetAdapters } = require('../../scripts/lib/install-targets/registry');
const {
GUIDED_HARNESS_IDS,
HARNESS_CAPABILITIES,
getHarnessCapability,
listGuidedHarnesses,
listHarnessCapabilities,
normalizeHarnessSelection,
} = require('../../scripts/lib/harness-capabilities');
function test(name, fn) {
try {
fn();
console.log(` \u2713 ${name}`);
return true;
} catch (error) {
console.log(` \u2717 ${name}`);
console.log(` Error: ${error.message}`);
return false;
}
}
function runTests() {
console.log('\n=== Testing harness capability catalog ===\n');
let passed = 0;
let failed = 0;
if (test('represents all 14 registered targets exactly once across 13 harnesses', () => {
const catalogTargetIds = HARNESS_CAPABILITIES.flatMap(harness => harness.targetIds);
const adapterTargetIds = listInstallTargetAdapters().map(adapter => adapter.target);
assert.strictEqual(HARNESS_CAPABILITIES.length, 13);
assert.strictEqual(new Set(catalogTargetIds).size, 14);
assert.deepStrictEqual([...catalogTargetIds].sort(), [...SUPPORTED_INSTALL_TARGETS].sort());
assert.deepStrictEqual([...catalogTargetIds].sort(), [...adapterTargetIds].sort());
})) passed++; else failed++;
if (test('only Claude, Codex, and Kimi are guided-ready', () => {
assert.deepStrictEqual(GUIDED_HARNESS_IDS, ['claude', 'codex', 'kimi']);
assert.deepStrictEqual(
listGuidedHarnesses().map(harness => harness.id),
['claude', 'codex', 'kimi']
);
assert.ok(HARNESS_CAPABILITIES
.filter(harness => !harness.guidedReady)
.every(harness => harness.availability === 'advanced'));
})) passed++; else failed++;
if (test('models reviewed guided install modes, roots, and scopes', () => {
const claude = getHarnessCapability('claude');
assert.deepStrictEqual(claude.targetIds, ['claude', 'claude-project']);
assert.strictEqual(claude.channel, 'native-plugin');
assert.strictEqual(claude.installMode, 'native-plugin');
assert.match(claude.destination, /selected Claude plugin scope/i);
assert.deepStrictEqual(claude.scopes, [
{ id: 'user', targetId: 'claude', root: '~/.claude' },
{ id: 'project', targetId: 'claude-project', root: './.claude' },
{ id: 'local', targetId: 'claude-project', root: './.claude' },
]);
const codex = getHarnessCapability('codex');
assert.deepStrictEqual(codex.targetIds, ['codex']);
assert.strictEqual(codex.channel, 'native-plugin');
assert.strictEqual(codex.installMode, 'native-plugin');
assert.match(codex.destination, /~\/\.codex/);
assert.deepStrictEqual(codex.scopes, [
{ id: 'native', targetId: 'codex', root: '~/.codex' },
]);
const kimi = getHarnessCapability('kimi');
assert.deepStrictEqual(kimi.targetIds, ['kimi']);
assert.strictEqual(kimi.channel, 'managed-project');
assert.strictEqual(kimi.installMode, 'managed-project');
assert.strictEqual(kimi.destination, './.kimi-code');
assert.deepStrictEqual(kimi.scopes, [
{ id: 'project', targetId: 'kimi', root: './.kimi-code' },
]);
})) passed++; else failed++;
if (test('keeps every advanced target attached to its registered root and scope', () => {
const expected = {
cursor: ['project', './.cursor'],
antigravity: ['project', './.agent'],
gemini: ['project', './.gemini'],
opencode: ['home', '~/.opencode'],
codebuddy: ['project', './.codebuddy'],
joycode: ['project', './.joycode'],
qwen: ['home', '~/.qwen'],
zed: ['project', './.zed'],
hermes: ['home', '~/.hermes'],
openclaw: ['home', '~/.openclaw'],
};
for (const [id, [scopeId, root]] of Object.entries(expected)) {
const harness = getHarnessCapability(id);
assert.strictEqual(harness.guidedReady, false, id);
assert.strictEqual(harness.availability, 'advanced', id);
assert.deepStrictEqual(harness.scopes, [
{ id: scopeId, targetId: id, root },
], id);
}
})) passed++; else failed++;
if (test('describes hook capability without claiming Kimi provider support is absent', () => {
assert.strictEqual(getHarnessCapability('claude').hooks.mode, 'profile-selection');
assert.strictEqual(getHarnessCapability('codex').hooks.mode, 'native-trust');
const kimiHooks = getHarnessCapability('kimi').hooks;
assert.strictEqual(kimiHooks.mode, 'not-configured');
assert.strictEqual(kimiHooks.eccConfigured, false);
assert.match(kimiHooks.note, /ECC hooks are not configured/i);
assert.strictEqual(kimiHooks.summary, kimiHooks.note);
assert.doesNotMatch(kimiHooks.note, /provider.*unsupported|Kimi.*unsupported/i);
})) passed++; else failed++;
if (test('does not advertise unregistered Copilot, Kiro, or Pi harnesses', () => {
for (const id of ['copilot', 'kiro', 'pi']) {
assert.strictEqual(getHarnessCapability(id), null);
assert.throws(
() => normalizeHarnessSelection(id),
/Unknown guided harness selection/
);
}
})) passed++; else failed++;
if (test('normalizes wizard selections into canonical guided order', () => {
assert.deepStrictEqual(
normalizeHarnessSelection(' KIMI CODE, Claude Code, kimi '),
['claude', 'kimi']
);
assert.deepStrictEqual(
normalizeHarnessSelection(['3', 'claude-project', 'Codex']),
['claude', 'codex', 'kimi']
);
assert.deepStrictEqual(normalizeHarnessSelection('all'), ['claude', 'codex', 'kimi']);
assert.deepStrictEqual(normalizeHarnessSelection('*'), ['claude', 'codex', 'kimi']);
})) passed++; else failed++;
if (test('rejects empty, ambiguous, advanced, and unknown wizard selections clearly', () => {
assert.throws(() => normalizeHarnessSelection(''), /At least one guided harness/);
assert.throws(() => normalizeHarnessSelection([]), /At least one guided harness/);
assert.throws(() => normalizeHarnessSelection('none'), /At least one guided harness/);
assert.throws(() => normalizeHarnessSelection('all,codex'), /cannot be combined/i);
assert.throws(() => normalizeHarnessSelection('cursor'), /advanced.*not guided-ready/i);
assert.throws(() => normalizeHarnessSelection('grok'), /Unknown guided harness selection/);
})) passed++; else failed++;
if (test('exports deeply frozen records while list helpers return safe array copies', () => {
assert.ok(Object.isFrozen(HARNESS_CAPABILITIES));
assert.ok(Object.isFrozen(HARNESS_CAPABILITIES[0]));
assert.ok(Object.isFrozen(HARNESS_CAPABILITIES[0].targetIds));
assert.ok(Object.isFrozen(HARNESS_CAPABILITIES[0].scopes));
assert.ok(Object.isFrozen(HARNESS_CAPABILITIES[0].scopes[0]));
assert.ok(Object.isFrozen(HARNESS_CAPABILITIES[0].hooks));
assert.ok(Object.isFrozen(GUIDED_HARNESS_IDS));
const first = listHarnessCapabilities();
first.pop();
assert.strictEqual(listHarnessCapabilities().length, 13);
const guided = listGuidedHarnesses();
guided.reverse();
assert.deepStrictEqual(
listGuidedHarnesses().map(harness => harness.id),
['claude', 'codex', 'kimi']
);
})) passed++; else failed++;
console.log(`\n${passed} passed, ${failed} failed\n`);
return failed === 0;
}
if (require.main === module) {
process.exit(runTests() ? 0 : 1);
}
module.exports = { runTests };