fix(cli): validate runtimes before uninstall
This commit is contained in:
1 parent
924bed6c21
commit
188b70655d
6 files changed
+243
-94
No files matched your search
@@ -1,11 +1,12 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { chmod, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
||||
import { chmod, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import test from 'node:test';
|
||||
|
||||
import { resolveInstallSecrets } from '../dist/cli-secrets.js';
|
||||
import { requirePhysicalDirectory } from '../dist/runtime-paths.js';
|
||||
|
||||
const PRIVATE_KEY = Buffer.alloc(32, 7).toString('base64url');
|
||||
const API_TOKEN = 'ely_test_token';
|
||||
@@ -135,6 +136,89 @@ test('CLI rejects agent IDs that escape the credential directory', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('uninstall validates the stored runtime identity before plugin changes', async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
|
||||
const runtimeDir = path.join(homeDir, '.elydora', 'stored-directory');
|
||||
try {
|
||||
await mkdir(runtimeDir, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(runtimeDir, 'config.json'),
|
||||
JSON.stringify({ agent_name: 'opencode', agent_id: 'different-agent' }),
|
||||
'utf-8',
|
||||
);
|
||||
const result = await runCli(['uninstall', '--agent', 'opencode'], homeDir);
|
||||
|
||||
assert.equal(result.code, 1);
|
||||
assert.match(result.stderr, /crosses its runtime directory/);
|
||||
assert.equal((await stat(runtimeDir)).isDirectory(), true);
|
||||
} finally {
|
||||
await rm(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('uninstall requires an explicit ID for ambiguous agent runtimes', async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
|
||||
try {
|
||||
for (const agentId of ['agent-1', 'agent-2']) {
|
||||
const runtimeDir = path.join(homeDir, '.elydora', agentId);
|
||||
await mkdir(runtimeDir, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(runtimeDir, 'config.json'),
|
||||
JSON.stringify({ agent_name: 'opencode', agent_id: agentId }),
|
||||
'utf-8',
|
||||
);
|
||||
}
|
||||
const result = await runCli(['uninstall', '--agent', 'opencode'], homeDir);
|
||||
|
||||
assert.equal(result.code, 1);
|
||||
assert.match(result.stderr, /Multiple installed agents/);
|
||||
assert.equal((await stat(path.join(homeDir, '.elydora', 'agent-1'))).isDirectory(), true);
|
||||
assert.equal((await stat(path.join(homeDir, '.elydora', 'agent-2'))).isDirectory(), true);
|
||||
} finally {
|
||||
await rm(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('runtime directory checks reject symbolic-link children', async (t) => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
|
||||
const targetDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-target-'));
|
||||
const runtimeRoot = path.join(homeDir, '.elydora');
|
||||
const linkPath = path.join(runtimeRoot, 'agent-1');
|
||||
try {
|
||||
await mkdir(runtimeRoot, { recursive: true });
|
||||
try {
|
||||
await symlink(targetDir, linkPath, 'dir');
|
||||
} catch (error) {
|
||||
t.skip(`Directory symbolic links are unavailable: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
requirePhysicalDirectory(linkPath),
|
||||
/not a physical directory/,
|
||||
);
|
||||
} finally {
|
||||
await rm(homeDir, { recursive: true, force: true });
|
||||
await rm(targetDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('status surfaces malformed runtime config', async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
|
||||
const runtimeDir = path.join(homeDir, '.elydora', 'agent-1');
|
||||
try {
|
||||
await mkdir(runtimeDir, { recursive: true });
|
||||
await writeFile(path.join(runtimeDir, 'config.json'), '{invalid-json', 'utf-8');
|
||||
|
||||
const result = await runCli(['status'], homeDir);
|
||||
|
||||
assert.equal(result.code, 1);
|
||||
assert.match(result.stderr, /Could not parse agent config/);
|
||||
} finally {
|
||||
await rm(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('CLI installs from owner-only files and protects persisted credentials', async () => {
|
||||
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
|
||||
const privateKeyFile = path.join(homeDir, 'private-key');
|
||||
|
||||
Reference in new issue
Block a user