Files

493 lines
20 KiB
JavaScript

import assert from 'node:assert/strict';
import { lstat, mkdir, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import {
VALID_PRIVATE_KEY,
assertManagedHandler,
cliPath,
configModuleUrl,
contractModuleUrl,
createFixture,
installConfig,
installationModuleUrl,
legacyGroup,
managedHandler,
parseSettings,
readSettings,
registryModuleUrl,
runNode,
runPlugin,
sourcesModuleUrl,
writeSettings,
} from '../test-support/qwen.mjs';
const GUARD_NAME = 'elydora-guard';
const AUDIT_NAME = 'elydora-audit';
async function assertMissing(filePath) {
await assert.rejects(lstat(filePath), { code: 'ENOENT' });
}
function assertManagedTriple(settings) {
const guard = managedHandler(settings, 'PreToolUse', GUARD_NAME);
const audit = managedHandler(settings, 'PostToolUse', AUDIT_NAME);
const failure = managedHandler(settings, 'PostToolUseFailure', AUDIT_NAME);
assertManagedHandler(guard, GUARD_NAME);
assertManagedHandler(audit, AUDIT_NAME);
assertManagedHandler(failure, AUDIT_NAME);
assert.equal(audit.command, failure.command);
return { guard, audit, failure };
}
function isolatedEnvironment(fixture) {
return {
HOME: fixture.homeDir,
USERPROFILE: fixture.homeDir,
QWEN_HOME: '',
QWEN_CODE_SYSTEM_DEFAULTS_PATH: path.join(fixture.rootDir, 'system-defaults.json'),
QWEN_CODE_SYSTEM_SETTINGS_PATH: path.join(fixture.rootDir, 'system-settings.json'),
QWEN_CODE_TRUSTED_FOLDERS_PATH: path.join(fixture.rootDir, 'trusted-folders.json'),
};
}
test('Qwen Code is registered in the SDK and CLI', async () => {
const { SUPPORTED_AGENTS } = await import(registryModuleUrl);
assert.deepEqual(SUPPORTED_AGENTS.get('qwen'), {
name: 'Qwen Code',
configDir: '~/.qwen',
configFile: 'settings.json',
});
const fixture = await createFixture();
try {
const result = await runNode(
['--no-warnings', cliPath, 'status'],
isolatedEnvironment(fixture),
fixture.projectDir,
);
assert.equal(result.code, 0, result.stderr);
assert.match(result.stdout, /Qwen Code \(qwen\)/);
} finally {
await fixture.close();
}
});
test('Qwen installs an exact three-event contract and preserves every source', async () => {
const existing = [
'{',
' // Keep this user preference.',
' "theme": "GitHub",',
' "hooks": {',
' "FutureEvent": [null],',
' "PreToolUse": [{ "matcher": "read_file", "hooks": [{ "type": "command", "command": "user-hook" }] }]',
' }',
'}',
'',
].join('\r\n');
const fixture = await createFixture({ settings: existing });
const workspacePath = path.join(fixture.projectDir, '.qwen', 'settings.json');
const defaultsPath = path.join(fixture.rootDir, 'system-defaults.json');
const systemPath = path.join(fixture.rootDir, 'system-settings.json');
await writeSettings(workspacePath, { owner: 'workspace' });
await writeSettings(defaultsPath, { owner: 'defaults' });
await writeSettings(systemPath, { owner: 'system' });
const environment = {
QWEN_CODE_SYSTEM_DEFAULTS_PATH: defaultsPath,
QWEN_CODE_SYSTEM_SETTINGS_PATH: systemPath,
};
try {
const first = await fixture.install({}, environment);
assert.equal(first.code, 0, first.stderr);
assert.match(first.stdout, /run \/hooks/i);
const installed = await readSettings(fixture.settingsPath);
assert.match(installed.raw, /Keep this user preference/);
assert.match(installed.raw, /\r\n/);
assert.equal(installed.settings.theme, 'GitHub');
assert.deepEqual(installed.settings.hooks.FutureEvent, [null]);
assert.equal(installed.settings.hooks.PreToolUse[0].hooks[0].command, 'user-hook');
assertManagedTriple(installed.settings);
for (const filePath of [
path.join(fixture.agentDir, 'config.json'),
path.join(fixture.agentDir, 'private.key'),
fixture.guardScriptPath,
fixture.hookScriptPath,
]) assert.equal((await lstat(filePath)).isFile(), true);
const second = await fixture.install({}, environment);
assert.equal(second.code, 0, second.stderr);
assert.equal(await readFile(fixture.settingsPath, 'utf-8'), installed.raw);
assert.deepEqual(parseSettings(await readFile(workspacePath, 'utf-8')), { owner: 'workspace' });
assert.deepEqual(parseSettings(await readFile(defaultsPath, 'utf-8')), { owner: 'defaults' });
assert.deepEqual(parseSettings(await readFile(systemPath, 'utf-8')), { owner: 'system' });
} finally {
await fixture.close();
}
});
test('Qwen resolves QWEN_HOME with official bootstrap precedence', async () => {
const fixture = await createFixture();
const firstHome = path.join(fixture.rootDir, 'first qwen home');
const secondHome = path.join(fixture.rootDir, 'second qwen home');
await writeSettings(
path.join(fixture.homeDir, '.qwen', '.env'),
`QWEN_HOME=${firstHome}\n`,
);
await writeSettings(path.join(fixture.homeDir, '.env'), `QWEN_HOME=${secondHome}\n`);
await writeSettings(path.join(firstHome, '.env'), 'QWEN_RUNTIME_DIR=runtime\n');
try {
const result = await fixture.install();
assert.equal(result.code, 0, result.stderr);
const selected = path.join(firstHome, 'settings.json');
assertManagedTriple((await readSettings(selected)).settings);
await assertMissing(path.join(secondHome, 'settings.json'));
await assertMissing(fixture.settingsPath);
const status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
assert.equal(status.configPath, selected);
} finally {
await fixture.close();
}
});
test('Qwen resolves explicit relative, tilde, and empty QWEN_HOME values', async () => {
for (const [value, expectedPath] of [
['relative-qwen', (fixture) => path.join(fixture.projectDir, 'relative-qwen', 'settings.json')],
['~/custom-qwen', (fixture) => path.join(fixture.homeDir, 'custom-qwen', 'settings.json')],
['', (fixture) => fixture.settingsPath],
]) {
const fixture = await createFixture();
await writeSettings(
path.join(fixture.homeDir, '.qwen', '.env'),
`QWEN_HOME=${path.join(fixture.rootDir, 'ignored-home')}\n`,
);
try {
const result = await fixture.install({}, { QWEN_HOME: value });
assert.equal(result.code, 0, result.stderr);
assertManagedTriple((await readSettings(expectedPath(fixture))).settings);
} finally {
await fixture.close();
}
}
});
test('Qwen validates current hook schemas and preserves future events', async () => {
const fixture = await createFixture({ settings: {
hooks: {
FutureEvent: [null],
MessageDisplay: [{ matcher: '[', hooks: [{
type: 'http',
url: 'http://127.0.0.1:8080/hook',
headers: { Authorization: 'Bearer ${TOKEN}' },
allowedEnvVars: ['TOKEN'],
timeout: 10,
once: true,
}] }],
Stop: [{ hooks: [{
type: 'prompt',
prompt: 'Evaluate $ARGUMENTS',
model: 'fast',
timeout: 30,
}] }],
},
} });
try {
const result = await fixture.install();
assert.equal(result.code, 0, result.stderr);
const settings = (await readSettings(fixture.settingsPath)).settings;
assert.deepEqual(settings.hooks.FutureEvent, [null]);
assert.equal(settings.hooks.MessageDisplay[0].matcher, '[');
assert.equal(settings.hooks.Stop[0].hooks[0].type, 'prompt');
} finally {
await fixture.close();
}
});
test('Qwen rejects malformed affected settings before runtime writes', async (t) => {
const cases = [
['syntax', '{ malformed', /parse Qwen Code user settings/i],
['root', '[]', /must contain a JSON object/i],
['trailing comma', '{ "theme": true, }', /parse Qwen Code user settings/i],
['duplicate', '{ "hooks": {}, "hooks": {} }', /duplicate field "hooks"/i],
['disable', '{ "disableAllHooks": "yes" }', /disableAllHooks.*boolean/i],
['hooks shape', '{ "hooks": [] }', /field "hooks" must be an object/i],
['event shape', '{ "hooks": { "PreToolUse": null } }', /must be an array/i],
['group shape', '{ "hooks": { "PreToolUse": [null] } }', /group.*must be an object/i],
['matcher shape', '{ "hooks": { "PreToolUse": [{ "matcher": 1, "hooks": [] }] } }', /matcher must be a string/i],
['matcher regex', '{ "hooks": { "PreToolUse": [{ "matcher": "[", "hooks": [] }] } }', /valid regular expression/i],
['notification matcher', '{ "hooks": { "Notification": [{ "matcher": "[", "hooks": [] }] } }', /valid regular expression/i],
['sequential', '{ "hooks": { "PreToolUse": [{ "sequential": 1, "hooks": [] }] } }', /sequential must be a boolean/i],
['hooks missing', '{ "hooks": { "PreToolUse": [{}] } }', /must contain a hooks array/i],
['handler shape', '{ "hooks": { "PreToolUse": [{ "hooks": [null] }] } }', /hooks\[0\].*object/i],
['handler type', '{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "function" }] }] } }', /unsupported type/i],
['command', '{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "command" }] }] } }', /non-empty command/i],
['http', '{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "http" }] }] } }', /non-empty url/i],
['prompt', '{ "hooks": { "Stop": [{ "hooks": [{ "type": "prompt" }] }] } }', /non-empty prompt/i],
['timeout', '{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "command", "command": "x", "timeout": -1 }] }] } }', /non-negative finite number/i],
['trust', '{ "security": { "folderTrust": { "enabled": "yes" } } }', /folderTrust.enabled.*boolean/i],
];
for (const [name, settings, pattern] of cases) {
await t.test(name, async () => {
const fixture = await createFixture({ settings });
try {
const result = await fixture.install();
assert.equal(result.code, 1);
assert.match(result.stderr, pattern);
assert.equal(await readFile(fixture.settingsPath, 'utf-8'), settings);
await assertMissing(fixture.agentDir);
} finally {
await fixture.close();
}
});
}
});
test('Qwen surfaces malformed read-only sources and home routing', async () => {
const system = await createFixture();
const systemPath = path.join(system.rootDir, 'system.json');
await writeSettings(systemPath, '{ malformed');
try {
const result = await system.install({}, { QWEN_CODE_SYSTEM_SETTINGS_PATH: systemPath });
assert.equal(result.code, 1);
assert.match(result.stderr, /parse Qwen Code system override settings/i);
assert.equal(await readFile(systemPath, 'utf-8'), '{ malformed');
await assertMissing(system.settingsPath);
await assertMissing(system.agentDir);
} finally {
await system.close();
}
const routing = await createFixture();
await mkdir(path.join(routing.homeDir, '.qwen', '.env'), { recursive: true });
try {
const result = await routing.install();
assert.equal(result.code, 1);
assert.match(result.stderr, /home environment.*physical file/i);
await assertMissing(routing.settingsPath);
await assertMissing(routing.agentDir);
} finally {
await routing.close();
}
});
test('Qwen transaction aborts when a read-only source changes after prepare', async () => {
const fixture = await createFixture();
const systemPath = path.join(fixture.rootDir, 'system.json');
const defaultsPath = path.join(fixture.rootDir, 'defaults.json');
await writeSettings(systemPath, { owner: 'before' });
const source = `
import { writeFile } from 'node:fs/promises';
import { readQwenSources } from ${JSON.stringify(sourcesModuleUrl)};
import { renderQwenDocument } from ${JSON.stringify(configModuleUrl)};
import {
AUDIT_HOOK_NAME,
GUARD_HOOK_NAME,
buildQwenGroup,
} from ${JSON.stringify(contractModuleUrl)};
import {
commitQwenInstallation,
preflightQwenInstallation,
prepareQwenInstallation,
} from ${JSON.stringify(installationModuleUrl)};
const config = JSON.parse(process.env.ELYDORA_TEST_CONFIG);
const sources = await readQwenSources();
const paths = await preflightQwenInstallation(config, sources);
const rendered = renderQwenDocument(sources.user, undefined, new Map([
['PreToolUse', buildQwenGroup(paths.guardPath, GUARD_HOOK_NAME)],
['PostToolUse', buildQwenGroup(paths.auditPath, AUDIT_HOOK_NAME)],
['PostToolUseFailure', buildQwenGroup(paths.auditPath, AUDIT_HOOK_NAME)],
]));
const prepared = await prepareQwenInstallation(config, sources, rendered);
await writeFile(process.env.ELYDORA_SYSTEM_PATH, '{"owner":"after"}\\n');
await commitQwenInstallation(prepared);
`;
try {
const result = await runNode(
['--input-type=module', '--eval', source],
{
HOME: fixture.homeDir,
USERPROFILE: fixture.homeDir,
QWEN_HOME: '',
QWEN_RUNTIME_DIR: '',
QWEN_CODE_SYSTEM_SETTINGS_PATH: systemPath,
QWEN_CODE_SYSTEM_DEFAULTS_PATH: defaultsPath,
QWEN_CODE_TRUSTED_FOLDERS_PATH: path.join(fixture.rootDir, 'trusted.json'),
ELYDORA_SYSTEM_PATH: systemPath,
ELYDORA_TEST_CONFIG: JSON.stringify(installConfig(fixture)),
},
fixture.projectDir,
);
assert.equal(result.code, 1);
assert.match(result.stderr, /system override settings changed during Qwen Code installation/i);
assert.deepEqual(parseSettings(await readFile(systemPath, 'utf-8')), { owner: 'after' });
await assertMissing(fixture.settingsPath);
await assertMissing(fixture.guardScriptPath);
await assertMissing(fixture.hookScriptPath);
await assertMissing(path.join(fixture.agentDir, 'config.json'));
await assertMissing(path.join(fixture.agentDir, 'private.key'));
} finally {
await fixture.close();
}
});
test('Qwen applies disableAllHooks precedence and workspace trust', async () => {
const defaults = await createFixture({ settings: { disableAllHooks: false } });
const defaultsPath = path.join(defaults.rootDir, 'defaults.json');
await writeSettings(defaultsPath, { disableAllHooks: true });
try {
assert.equal((await defaults.install({}, {
QWEN_CODE_SYSTEM_DEFAULTS_PATH: defaultsPath,
})).code, 0);
} finally {
await defaults.close();
}
const system = await createFixture();
const systemPath = path.join(system.rootDir, 'system.json');
await writeSettings(systemPath, { disableAllHooks: true });
try {
const result = await system.install({}, { QWEN_CODE_SYSTEM_SETTINGS_PATH: systemPath });
assert.equal(result.code, 1);
assert.match(result.stderr, /disableAllHooks.*system override/i);
await assertMissing(system.agentDir);
} finally {
await system.close();
}
const untrusted = await createFixture({ settings: {
security: { folderTrust: { enabled: true } },
} });
await writeSettings(path.join(untrusted.projectDir, '.qwen', 'settings.json'), {
disableAllHooks: true,
});
await writeSettings(path.join(untrusted.homeDir, '.qwen', 'trustedFolders.json'), {
[untrusted.projectDir]: 'DO_NOT_TRUST',
});
try {
const result = await untrusted.install();
assert.equal(result.code, 0, result.stderr);
assertManagedTriple((await readSettings(untrusted.settingsPath)).settings);
} finally {
await untrusted.close();
}
});
test('Qwen migrates exact legacy handlers and preserves ownership lookalikes', async () => {
const fixture = await createFixture();
const lookalike = legacyGroup(fixture.guardScriptPath);
lookalike.hooks[0].timeout = 9_000;
await writeSettings(fixture.settingsPath, {
hooks: {
PreToolUse: [legacyGroup(fixture.guardScriptPath), lookalike],
PostToolUse: [legacyGroup(fixture.hookScriptPath)],
},
});
try {
assert.equal((await fixture.install()).code, 0);
let settings = (await readSettings(fixture.settingsPath)).settings;
assertManagedTriple(settings);
assert(settings.hooks.PreToolUse.some((group) => group.hooks[0].timeout === 9_000));
settings.hooks.PreToolUse.at(-1).userField = 'preserve-group';
settings.hooks.PreToolUse.at(-1).hooks.push({ type: 'command', command: 'user-command' });
await writeSettings(fixture.settingsPath, settings);
const uninstall = await runPlugin(fixture, 'uninstall', 'agent-1');
assert.equal(uninstall.code, 0, uninstall.stderr);
settings = (await readSettings(fixture.settingsPath)).settings;
assert(settings.hooks.PreToolUse.some((group) => group.hooks[0].timeout === 9_000));
assert(settings.hooks.PreToolUse.some((group) => (
group.hooks.some((handler) => handler.command === 'user-command')
)));
assert.equal(settings.hooks.PostToolUse, undefined);
assert.equal(settings.hooks.PostToolUseFailure, undefined);
} finally {
await fixture.close();
}
});
test('Qwen uninstall removes an owned file and preserves user settings', async () => {
const user = await createFixture({ settings: { theme: 'GitHub', hooks: { Notification: [] } } });
try {
assert.equal((await user.install()).code, 0);
assert.equal((await runPlugin(user, 'uninstall', 'agent-1')).code, 0);
assert.deepEqual((await readSettings(user.settingsPath)).settings, {
theme: 'GitHub',
hooks: { Notification: [] },
});
} finally {
await user.close();
}
const owned = await createFixture();
try {
assert.equal((await owned.install()).code, 0);
assert.match(await readFile(owned.settingsPath, 'utf-8'), /^\/\/ Managed by Elydora/);
assert.equal((await runPlugin(owned, 'uninstall', 'agent-1')).code, 0);
await assertMissing(owned.settingsPath);
} finally {
await owned.close();
}
});
test('Qwen status requires exact hooks and strict runtime identity', async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
let status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
assert.equal(status.installed, true);
const settings = (await readSettings(fixture.settingsPath)).settings;
settings.hooks.PostToolUseFailure.push(settings.hooks.PostToolUseFailure.at(-1));
await writeSettings(fixture.settingsPath, settings);
status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
assert.equal(status.installed, false);
assert.equal((await fixture.install()).code, 0);
await writeFile(path.join(fixture.agentDir, 'private.key'), 'invalid');
const invalid = await runPlugin(fixture, 'status', null);
assert.equal(invalid.code, 1);
assert.match(invalid.stderr, /private key is invalid/i);
assert.equal((await fixture.install()).code, 0);
await writeFile(fixture.guardScriptPath, 'tampered');
status = JSON.parse((await runPlugin(fixture, 'status', null)).stdout);
assert.equal(status.installed, false);
} finally {
await fixture.close();
}
});
test('Qwen CLI completes install, status, and uninstall end to end', async () => {
const fixture = await createFixture();
const privateKeyFile = path.join(fixture.rootDir, 'install-private.key');
const tokenFile = path.join(fixture.rootDir, 'install-token.txt');
const environment = isolatedEnvironment(fixture);
try {
await writeFile(privateKeyFile, `${VALID_PRIVATE_KEY}\n`, { mode: 0o600 });
await writeFile(tokenFile, 'token-1\n', { mode: 0o600 });
const install = await runNode([
'--no-warnings', cliPath, 'install',
'--agent', 'qwen',
'--org_id', 'org-1',
'--agent_id', 'agent-1',
'--kid', 'kid-1',
'--private_key_file', privateKeyFile,
'--token_file', tokenFile,
'--base_url', fixture.baseUrl,
], environment, fixture.projectDir);
assert.equal(install.code, 0, install.stderr);
assertManagedTriple((await readSettings(fixture.settingsPath)).settings);
const status = await runNode(
['--no-warnings', cliPath, 'status'],
environment,
fixture.projectDir,
);
assert.equal(status.code, 0, status.stderr);
assert.match(status.stdout, /Qwen Code \(qwen\) \[installed\]/);
const uninstall = await runNode([
'--no-warnings', cliPath, 'uninstall',
'--agent', 'qwen',
'--agent_id', 'agent-1',
], environment, fixture.projectDir);
assert.equal(uninstall.code, 0, uninstall.stderr);
await assertMissing(fixture.settingsPath);
await assertMissing(fixture.agentDir);
} finally {
await fixture.close();
}
});