Files
Elydora-Open-Source/sdks/node/test/cursor-plugin.test.mjs
T
雷电芽衣 98c61a0b6f sdks/node: mirror Elydora-Node-SDK 2.0.1
Rebuilt from the standalone 2101cb7 tree (durable transactions, Kiro IDE
adapter, hook retry with a bounded chain lock, version 2.0.1) with the
Open-Source API trims re-applied: no webhooks, members, admin events,
deep health, token rotation, or agent integration updates.

Claude-Session: https://claude.ai/code/session_01QoUjdc7HGKTsgj5GhG8HvF
2026-09-02 11:01:55 -04:00

282 lines
10 KiB
JavaScript

import assert from 'node:assert/strict';
import {
lstat,
mkdir,
readFile,
readdir,
rm,
stat,
symlink,
writeFile,
} from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import {
assertNativeHandler,
createFixture,
installConfig,
legacyHandler,
managedHandler,
registryModuleUrl,
runPlugin,
writeJson,
} from '../test-support/cursor-test-helpers.mjs';
test('Cursor is registered with the native user hook file', async () => {
const { SUPPORTED_AGENTS } = await import(registryModuleUrl);
assert.deepEqual(SUPPORTED_AGENTS.get('cursor'), {
name: 'Cursor CLI',
configDir: '~/.cursor',
configFile: 'hooks.json',
});
});
test('Cursor install preserves hooks, migrates legacy entries, and is idempotent', async () => {
const fixture = await createFixture();
try {
await writeJson(fixture.configPath, {
description: 'user-owned',
hooks: {
sessionStart: [{ command: 'user-session' }],
preToolUse: [
{ command: 'user-pre' },
legacyHandler(fixture.guardScriptPath),
],
postToolUse: [legacyHandler(fixture.hookScriptPath)],
postToolUseFailure: [legacyHandler(fixture.hookScriptPath)],
},
});
const first = await fixture.install();
assert.equal(first.code, 0, first.stderr);
const second = await fixture.install();
assert.equal(second.code, 0, second.stderr);
const config = JSON.parse(await readFile(fixture.configPath, 'utf-8'));
assert.equal(config.version, 1);
assert.equal(config.description, 'user-owned');
assert.deepEqual(config.hooks.sessionStart, [{ command: 'user-session' }]);
assert.deepEqual(config.hooks.preToolUse[0], { command: 'user-pre' });
assert.equal(config.hooks.preToolUse.length, 2);
assert.equal(config.hooks.postToolUse.length, 1);
assert.equal(config.hooks.postToolUseFailure.length, 1);
assertNativeHandler(managedHandler(config, 'preToolUse', 'guard.js'));
assertNativeHandler(managedHandler(config, 'postToolUse', 'hook.js'));
assertNativeHandler(managedHandler(config, 'postToolUseFailure', 'hook.js'));
} finally {
await fixture.close();
}
});
test('Cursor status requires three handlers, matching identity, and every runtime secret', async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
let status = await runPlugin(fixture, 'status', null);
assert.equal(status.code, 0, status.stderr);
assert.equal(JSON.parse(status.stdout).installed, true);
const hooks = JSON.parse(await readFile(fixture.configPath, 'utf-8'));
delete hooks.hooks.postToolUseFailure;
await writeJson(fixture.configPath, hooks);
status = await runPlugin(fixture, 'status', null);
assert.equal(JSON.parse(status.stdout).hookConfigured, false);
assert.equal((await fixture.install()).code, 0);
const keyPath = path.join(fixture.agentDir, 'private.key');
await rm(keyPath);
status = await runPlugin(fixture, 'status', null);
assert.equal(JSON.parse(status.stdout).installed, false);
await writeFile(keyPath, 'restored', { mode: 0o600 });
await writeJson(path.join(fixture.agentDir, 'config.json'), {
agent_id: 'another-agent',
agent_name: 'cursor',
});
status = await runPlugin(fixture, 'status', null);
assert.equal(JSON.parse(status.stdout).installed, false);
await writeFile(path.join(fixture.agentDir, 'config.json'), '{ malformed');
status = await runPlugin(fixture, 'status', null);
assert.equal(status.code, 1);
assert.match(status.stderr, /parse Elydora runtime config/i);
} finally {
await fixture.close();
}
});
test('Cursor uninstall removes exact ownership and preserves user entries', async () => {
const fixture = await createFixture({
existingConfig: { version: 1, hooks: { sessionStart: [{ command: 'keep' }] } },
});
try {
assert.equal((await fixture.install()).code, 0);
const config = JSON.parse(await readFile(fixture.configPath, 'utf-8'));
for (const [event, script] of [
['preToolUse', 'guard.js'],
['postToolUse', 'hook.js'],
['postToolUseFailure', 'hook.js'],
]) {
const other = structuredClone(managedHandler(config, event, script));
other.command = other.command.replaceAll('agent-1', 'agent-10');
config.hooks[event].push(other);
}
config.hooks.preToolUse.push({ command: 'user-pre' });
await writeJson(fixture.configPath, config);
const result = await runPlugin(fixture, 'uninstall', 'agent-1');
assert.equal(result.code, 0, result.stderr);
const remaining = JSON.parse(await readFile(fixture.configPath, 'utf-8'));
assert.deepEqual(remaining.hooks.sessionStart, [{ command: 'keep' }]);
assert.equal(remaining.hooks.preToolUse.length, 2);
assert.equal(remaining.hooks.postToolUse.length, 1);
assert.equal(remaining.hooks.postToolUseFailure.length, 1);
assert.match(remaining.hooks.preToolUse[0].command, /agent-10/);
} finally {
await fixture.close();
}
});
test('Cursor rejects malformed, duplicate, versionless user, and invalid configs before writes', async () => {
const invalidConfigs = [
'{ malformed',
'[]\n',
'{"hooks":{}}\n',
'{"hooks":{"preToolUse":[{"command":"user"}]}}\n',
'{"version":2,"hooks":{}}\n',
'{"version":1,"hooks":null}\n',
'{"version":1,"hooks":{"preToolUse":null}}\n',
'{"version":1,"hooks":{"preToolUse":[null]}}\n',
'{"version":1,"version":1,"hooks":{}}\n',
'{"version":1,"hooks":{},}\n',
];
for (const existingConfig of invalidConfigs) {
const fixture = await createFixture({ existingConfig });
try {
const before = await readFile(fixture.configPath, 'utf-8');
const result = await fixture.install();
assert.equal(result.code, 1, `accepted ${existingConfig}`);
assert.equal(await readFile(fixture.configPath, 'utf-8'), before);
await assert.rejects(lstat(path.join(fixture.homeDir, '.elydora')), { code: 'ENOENT' });
} finally {
await fixture.close();
}
}
});
test('Cursor confines runtime scripts to the managed agent directory', async () => {
for (const field of ['guardScriptPath', 'hookScriptPath']) {
const fixture = await createFixture();
try {
const result = await runPlugin(fixture, 'install', installConfig(fixture, {
[field]: path.join(fixture.homeDir, `unmanaged-${field}.js`),
}));
assert.equal(result.code, 1);
assert.match(result.stderr, /managed agent directory/i);
await assert.rejects(readFile(fixture.configPath), { code: 'ENOENT' });
} finally {
await fixture.close();
}
}
});
test('Cursor rejects a symbolic-link config and preserves its target', async (t) => {
const fixture = await createFixture();
try {
const target = path.join(fixture.homeDir, 'cursor-hooks-target.json');
const original = '{"version":1,"hooks":{}}\n';
await mkdir(fixture.homeDir, { recursive: true });
await writeFile(target, original);
await mkdir(path.dirname(fixture.configPath), { recursive: true });
try {
await symlink(target, fixture.configPath);
} catch (error) {
if (error?.code === 'EPERM') {
t.skip(`symbolic links unavailable: ${error.message}`);
return;
}
throw error;
}
const result = await fixture.install();
assert.equal(result.code, 1);
assert.match(result.stderr, /physical file/i);
assert.equal(await readFile(target, 'utf-8'), original);
assert.equal((await lstat(fixture.configPath)).isSymbolicLink(), true);
} finally {
await fixture.close();
}
});
test('Cursor status rejects symbolic-link runtime files', async (t) => {
for (const name of ['config', 'key', 'guard', 'audit']) {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
const filePath = {
config: path.join(fixture.agentDir, 'config.json'),
key: path.join(fixture.agentDir, 'private.key'),
guard: fixture.guardScriptPath,
audit: fixture.hookScriptPath,
}[name];
const contents = await readFile(filePath);
const target = path.join(fixture.homeDir, `${name}-runtime-target`);
await writeFile(target, contents);
await rm(filePath);
try {
await symlink(target, filePath);
} catch (error) {
if (error?.code === 'EPERM') {
t.skip(`symbolic links unavailable: ${error.message}`);
return;
}
throw error;
}
const status = await runPlugin(fixture, 'status', null);
assert.equal(status.code, 1);
assert.match(status.stderr, /physical file/i);
} finally {
await fixture.close();
}
}
});
test('Cursor removes an entirely managed config and leaves an absent config absent', async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
assert.equal((await runPlugin(fixture, 'uninstall', 'agent-1')).code, 0);
await assert.rejects(readFile(fixture.configPath), { code: 'ENOENT' });
assert.equal((await runPlugin(fixture, 'uninstall', 'agent-1')).code, 0);
await assert.rejects(readFile(fixture.configPath), { code: 'ENOENT' });
} finally {
await fixture.close();
}
});
test('Cursor installation writes private files and leaves no transaction files', async () => {
const fixture = await createFixture();
try {
const result = await fixture.install();
assert.equal(result.code, 0, result.stderr);
const names = [
...await readdir(fixture.agentDir),
...await readdir(path.dirname(fixture.configPath)),
];
assert.equal(names.some((name) => /\.(tmp|rollback)$/.test(name)), false);
if (process.platform !== 'win32') {
for (const filePath of [
fixture.configPath,
path.join(fixture.agentDir, 'config.json'),
path.join(fixture.agentDir, 'private.key'),
]) {
assert.equal((await stat(filePath)).mode & 0o777, 0o600);
}
for (const filePath of [fixture.guardScriptPath, fixture.hookScriptPath]) {
assert.equal((await stat(filePath)).mode & 0o777, 0o700);
}
}
} finally {
await fixture.close();
}
});