mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-28 12:35:13 +02:00
158 lines
8.3 KiB
JavaScript
158 lines
8.3 KiB
JavaScript
'use strict';
|
|
const assert = require('node:assert/strict');
|
|
const crypto = require('node:crypto');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
const { withFixture } = require('./helpers/context-fixture');
|
|
const { launchTaskContext } = require('../../scripts/lib/context-profile-launch');
|
|
const input = { sessionId: 'launch', taskId: 'task', revision: 1, phase: 'implement', query: 'Explain a Python list', explicitIds: ['skill:feature'] };
|
|
|
|
function nativeFixture(repoRoot) {
|
|
const home = path.join(fs.realpathSync(repoRoot), 'isolated-home');
|
|
const codexPath = path.join(fs.realpathSync(repoRoot), 'provider-bin');
|
|
const bytes = Buffer.from('7f454c460102030405060708', 'hex');
|
|
fs.writeFileSync(codexPath, bytes);
|
|
return { home, codexHome: path.join(home, '.codex'), codexPath,
|
|
executableDigest: crypto.createHash('sha256').update(bytes).digest('hex') };
|
|
}
|
|
|
|
test('Auto launcher resolves context and supplies it on stdin without permission overrides', () => withFixture(repoRoot => {
|
|
let called = 0;
|
|
const result = launchTaskContext({ repoRoot, task: input, target: 'codex', execute(command, args, options) {
|
|
called++;
|
|
assert.equal(command, 'codex');
|
|
assert.deepEqual(args, ['exec', '-']);
|
|
assert.ok(options.input.includes(input.query));
|
|
assert.match(options.input, /# feature/);
|
|
assert.equal(options.shell, false);
|
|
assert.equal(options.killSignal, 'SIGKILL');
|
|
return { status: 0, stdout: 'A list is a sequence.', stderr: '' };
|
|
} });
|
|
assert.equal(called, 1);
|
|
assert.equal(result.status, 'completed');
|
|
assert.equal(result.taskSuccess, 'unverified');
|
|
assert.equal(result.selection.receipt.loadedIds.length, 1);
|
|
}));
|
|
|
|
test('dry-run neither loads bodies nor invokes a provider', () => withFixture(repoRoot => {
|
|
const result = launchTaskContext({ repoRoot, task: input, dryRun: true, execute() { assert.fail('must not execute'); } });
|
|
assert.equal(result.status, 'proposed');
|
|
assert.deepEqual(result.selection.loadedIds, []);
|
|
}));
|
|
|
|
test('Claude uses documented print mode and receives context as ordinary input', () => withFixture(repoRoot => {
|
|
launchTaskContext({ repoRoot, task: input, target: 'claude', execute(command, args) {
|
|
assert.equal(command, 'claude');
|
|
assert.deepEqual(args, ['--print']);
|
|
return { status: 0, stdout: 'ok', stderr: '' };
|
|
} });
|
|
}));
|
|
|
|
test('unsupported providers and failed selection cannot invoke a process', () => withFixture(repoRoot => {
|
|
assert.throws(() => launchTaskContext({ repoRoot, task: input, target: 'pi' }), /unsupported/i);
|
|
assert.throws(() => launchTaskContext({ repoRoot, task: input, exclude: ['skill:feature'], execute() { assert.fail('must not execute'); } }), /excluded/);
|
|
}));
|
|
|
|
test('provider failure is distinct from successful task completion', () => withFixture(repoRoot => {
|
|
const result = launchTaskContext({ repoRoot, task: input, execute: () => ({ status: 2, stdout: '', stderr: 'authentication required' }) });
|
|
assert.equal(result.status, 'failed');
|
|
assert.equal(result.exitCode, 2);
|
|
assert.equal(result.taskSuccess, 'unverified');
|
|
}));
|
|
|
|
test('isolated native launches replace every provider home without mutating the parent environment', () => withFixture(repoRoot => {
|
|
const nativeEnvironment = nativeFixture(repoRoot);
|
|
const before = { ...process.env };
|
|
// Windows may expose the inherited key as Path while process.env resolves PATH case-insensitively.
|
|
const inheritedPath = process.env.PATH;
|
|
let called = false;
|
|
const result = launchTaskContext({ repoRoot, task: input, nativeEnvironment, execute(command, args, options) {
|
|
called = true;
|
|
assert.equal(command, nativeEnvironment.codexPath);
|
|
assert.deepEqual(args, ['exec', '-']);
|
|
assert.notEqual(options.env, process.env);
|
|
assert.equal(options.env.HOME, nativeEnvironment.home);
|
|
assert.equal(options.env.USERPROFILE, nativeEnvironment.home);
|
|
assert.equal(options.env.CODEX_HOME, nativeEnvironment.codexHome);
|
|
assert.equal(options.env.PATH, inheritedPath);
|
|
for (const key of ['AWS_ACCESS_KEY_ID', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'HTTP_PROXY', 'NODE_OPTIONS']) {
|
|
assert.equal(options.env[key], undefined);
|
|
}
|
|
assert.equal(options.shell, false);
|
|
assert.equal(options.timeout, 120000);
|
|
assert.equal(options.killSignal, 'SIGKILL');
|
|
assert.equal(options.maxBuffer, 1024 * 1024);
|
|
return { status: 0, stdout: 'ok' };
|
|
} });
|
|
assert.equal(called, true);
|
|
assert.equal(result.providerConfiguration, 'isolated-native-generation');
|
|
assert.deepEqual({ ...process.env }, before);
|
|
}));
|
|
|
|
test('isolated native dry-run avoids provider calls and leaves context unloaded', () => withFixture(repoRoot => {
|
|
const result = launchTaskContext({ repoRoot, task: input, dryRun: true,
|
|
nativeEnvironment: nativeFixture(repoRoot),
|
|
execute() { assert.fail('Dry-run must not invoke a provider'); } });
|
|
assert.equal(result.status, 'proposed');
|
|
assert.equal(result.providerConfiguration, 'isolated-native-generation');
|
|
assert.deepEqual(result.selection.resources, []);
|
|
}));
|
|
|
|
test('invalid native environment and empty query fail before provider calls', () => withFixture(repoRoot => {
|
|
const execute = () => assert.fail('Invalid launch must not invoke a provider');
|
|
for (const nativeEnvironment of [{}, { home: 'relative', codexHome: repoRoot },
|
|
{ home: repoRoot, codexHome: 'relative' }, { home: repoRoot, codexHome: repoRoot },
|
|
{ ...nativeFixture(repoRoot), codexPath: 'relative' },
|
|
{ ...nativeFixture(repoRoot), executableDigest: 'not-a-digest' }]) {
|
|
assert.throws(() => launchTaskContext({ repoRoot, task: input, nativeEnvironment, execute }), /Invalid isolated/);
|
|
}
|
|
assert.throws(() => launchTaskContext({ repoRoot, task: input, target: 'claude', execute,
|
|
nativeEnvironment: { home: repoRoot, codexHome: repoRoot } }), /Invalid isolated/);
|
|
assert.throws(() => launchTaskContext({ repoRoot, task: { ...input, query: ' ' }, execute }), /non-empty query/);
|
|
}));
|
|
|
|
test('pinned native executable digest mismatch stops before any provider call', () => withFixture(repoRoot => {
|
|
const nativeEnvironment = { ...nativeFixture(repoRoot), executableDigest: '0'.repeat(64) };
|
|
assert.throws(() => launchTaskContext({ repoRoot, task: input, nativeEnvironment,
|
|
execute() { assert.fail('Mismatched executable must never run'); } }), /executable.*changed|digest.*mismatch/i);
|
|
}));
|
|
|
|
test('native executable drift during Auto proposal prevents the task process', () => withFixture(repoRoot => {
|
|
const nativeEnvironment = nativeFixture(repoRoot);
|
|
fs.writeFileSync(path.join(repoRoot, 'skills/feature/SKILL.md'),
|
|
'---\nname: feature\ndescription: Handle database changes\n---\nUse an explicit transaction.');
|
|
const task = { ...input, explicitIds: [], query: 'Handle database changes' };
|
|
let calls = 0;
|
|
assert.throws(() => launchTaskContext({ repoRoot, task, nativeEnvironment, execute(command, args, options) {
|
|
calls++;
|
|
assert.equal(command, nativeEnvironment.codexPath);
|
|
assert.ok(args.includes('read-only'));
|
|
assert.equal(options.env.CODEX_HOME, nativeEnvironment.codexHome);
|
|
fs.appendFileSync(nativeEnvironment.codexPath, Buffer.from([9]));
|
|
return { status: 0, stdout: '{"selectedIds":["skill:feature"]}' };
|
|
} }), /executable.*changed|digest.*mismatch/i);
|
|
assert.equal(calls, 1);
|
|
}));
|
|
|
|
test('configured-state refusal precedes the Auto proposal process', () => withFixture(repoRoot => {
|
|
fs.writeFileSync(path.join(repoRoot, 'skills/feature/SKILL.md'),
|
|
'---\nname: feature\ndescription: Handle database changes\n---\nUse an explicit transaction.');
|
|
assert.throws(() => launchTaskContext({ repoRoot,
|
|
task: { ...input, explicitIds: [], query: 'Handle database changes' },
|
|
assertCurrent() { throw new Error('Stored profile changed'); },
|
|
execute() { assert.fail('Stale state must not start proposal'); } }), /Stored profile changed/);
|
|
}));
|
|
|
|
test('spawn failures and timeout signals remain unsuccessful without a native exit status', () => withFixture(repoRoot => {
|
|
for (const error of [new Error('spawn codex ENOENT'), new Error('spawn codex ETIMEDOUT')]) {
|
|
const result = launchTaskContext({ repoRoot, task: input,
|
|
execute: () => ({ status: null, signal: 'SIGTERM', error }) });
|
|
assert.equal(result.status, 'failed');
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(result.output, '');
|
|
assert.equal(result.error, error.message);
|
|
assert.equal(result.taskSuccess, 'unverified');
|
|
}
|
|
}));
|