493 lines
20 KiB
JavaScript
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();
|
|
}
|
|
});
|