mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
* test: strengthen suite oracles and cleanup ownership * test: reuse setup across independent scenarios (#8153) * Potential fix for pull request finding 'CodeQL / Improper code sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
320 lines
11 KiB
TypeScript
320 lines
11 KiB
TypeScript
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { parse, stringify } from "yaml";
|
|
import { expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const configuration = (
|
|
version: "1.1" | "1.2",
|
|
existing: boolean,
|
|
aliased = true,
|
|
) => {
|
|
const defaults =
|
|
(version === "1.2" ? "%YAML 1.2\n---\n" : "") +
|
|
"# Personal settings\nmodel: custom\nmask: 0x1F\n" +
|
|
"server_defaults: &servers\n" +
|
|
(existing
|
|
? " rea: &server\n command: npx\n args: [ '-y', 'rea-agents@6.1.0', 'mcp' ]\n env:\n FLAG: 'on'\n other: *server\n"
|
|
: " other: &server\n command: other-server\n args: [ 'on', '0600' ]\n");
|
|
return {
|
|
defaults,
|
|
text:
|
|
defaults +
|
|
(aliased
|
|
? "mcp_servers: *servers # Selected table\n"
|
|
: "mcp_servers:\n other: *server\n") +
|
|
"observer: *server\n",
|
|
};
|
|
};
|
|
|
|
for (const version of ["1.1", "1.2"] as const) {
|
|
cliTest(
|
|
`Hermes YAML ${version} uninstall preserves an aliased table without REA`,
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory(
|
|
"rea-hermes-absent-uninstall-",
|
|
);
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
const original = configuration(version, false).text;
|
|
await mkdir(profile);
|
|
await writeFile(config, original);
|
|
const removed = await cli.run({
|
|
arguments: ["uninstall", "--json"],
|
|
cwd: home,
|
|
environment: {
|
|
USERPROFILE: home,
|
|
HERMES_HOME: profile,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
expect(removed.exitCode).toBe(0);
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
await expect(access(`${config}.rea.backup`)).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
},
|
|
);
|
|
|
|
for (const existing of [false, true]) {
|
|
cliTest(
|
|
`Hermes YAML ${version} ${existing ? "refreshes" : "adds"} REA in an aliased table without changing its source`,
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-hermes-table-alias-");
|
|
const ghidra = join(home, "missing-ghidra");
|
|
const java = join(home, "missing-java");
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
const original = configuration(version, existing);
|
|
const before = parse(original.text, { version });
|
|
await mkdir(profile);
|
|
await writeFile(config, original.text);
|
|
const run = (arguments_: readonly string[]) =>
|
|
cli.run({
|
|
arguments: arguments_,
|
|
cwd: home,
|
|
environment: {
|
|
USERPROFILE: home,
|
|
HERMES_HOME: profile,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
GHIDRA_INSTALL_DIR: ghidra,
|
|
JAVA_HOME: java,
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
const setup = [
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--skill=false",
|
|
"--yes",
|
|
"--json",
|
|
];
|
|
const applied = await run(setup);
|
|
expect(applied.exitCode).toBe(0);
|
|
expect(applied.json).toMatchObject({ status: "ready" });
|
|
const configured = await readFile(config, "utf8");
|
|
const parsed = parse(configured, { version });
|
|
expect(parsed.server_defaults).toEqual(before.server_defaults);
|
|
expect(parsed.observer).toEqual(before.observer);
|
|
expect(parsed.mcp_servers.other).toEqual(before.mcp_servers.other);
|
|
expect(parsed.mcp_servers.rea).toMatchObject({ enabled: true });
|
|
if (existing)
|
|
expect(parsed.mcp_servers.rea.env).toEqual({
|
|
FLAG: "on",
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
GHIDRA_INSTALL_DIR: ghidra,
|
|
JAVA_HOME: java,
|
|
});
|
|
expect(configured.startsWith(original.defaults)).toBe(true);
|
|
expect(configured).toContain("# Selected table");
|
|
expect(configured.match(/&servers\b/gu)).toHaveLength(1);
|
|
expect(configured.match(/&server\b/gu)).toHaveLength(1);
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(
|
|
original.text,
|
|
);
|
|
const doctor = await run(["doctor", "--client", "hermes", "--json"]);
|
|
expect(doctor.exitCode).toBe(0);
|
|
expect(doctor.json).toMatchObject({
|
|
healthy: true,
|
|
identity: { registrations: [{ client: "hermes", state: "aligned" }] },
|
|
});
|
|
const repeat = await run(setup);
|
|
expect(repeat.exitCode).toBe(0);
|
|
expect(repeat.json).toMatchObject({
|
|
status: "ready",
|
|
plannedActions: [],
|
|
appliedActions: [],
|
|
});
|
|
expect(await readFile(config, "utf8")).toBe(configured);
|
|
const removed = await run(["uninstall", "--json"]);
|
|
expect(removed.exitCode).toBe(0);
|
|
const remaining = parse(await readFile(config, "utf8"), { version });
|
|
expect(remaining.mcp_servers).toEqual({
|
|
other: before.mcp_servers.other,
|
|
});
|
|
expect(remaining.server_defaults).toEqual(before.server_defaults);
|
|
expect(remaining.observer).toEqual(before.observer);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
cliTest("Hermes still registers into an explicit table", async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-hermes-explicit-table-");
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
const original = configuration("1.1", false, false);
|
|
await mkdir(profile);
|
|
await writeFile(config, original.text);
|
|
const applied = await cli.run({
|
|
arguments: [
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--skill=false",
|
|
"--yes",
|
|
"--json",
|
|
],
|
|
cwd: home,
|
|
environment: {
|
|
USERPROFILE: home,
|
|
HERMES_HOME: profile,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
expect(applied.exitCode).toBe(0);
|
|
expect(applied.json).toMatchObject({ status: "ready" });
|
|
const parsed = parse(await readFile(config, "utf8"));
|
|
expect(parsed.mcp_servers.other).toEqual(
|
|
parse(original.text).mcp_servers.other,
|
|
);
|
|
expect(parsed.mcp_servers.rea).toMatchObject({ enabled: true });
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original.text);
|
|
});
|
|
|
|
for (const version of ["1.1", "1.2"] as const) {
|
|
cliTest(
|
|
`Hermes YAML ${version} preserves shadowed alias bindings`,
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-hermes-shadowed-anchor-");
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
const original =
|
|
(version === "1.2" ? "%YAML 1.2\n---\n" : "") +
|
|
"first: &server\n command: original-server\n" +
|
|
"server_defaults: &servers\n other: *server\n" +
|
|
"replacement: &server\n command: replacement-server\n" +
|
|
"mcp_servers: *servers\nobserver: *server\n";
|
|
const before = parse(original, { version });
|
|
await mkdir(profile);
|
|
await writeFile(config, original);
|
|
const applied = await cli.run({
|
|
arguments: [
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--skill=false",
|
|
"--yes",
|
|
"--json",
|
|
],
|
|
cwd: home,
|
|
environment: {
|
|
USERPROFILE: home,
|
|
HERMES_HOME: profile,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
expect(applied.exitCode).toBe(0);
|
|
const configured = parse(await readFile(config, "utf8"), { version });
|
|
expect(configured.mcp_servers.other).toEqual(before.mcp_servers.other);
|
|
expect(configured.server_defaults).toEqual(before.server_defaults);
|
|
expect(configured.observer).toEqual(before.observer);
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
|
},
|
|
);
|
|
}
|
|
|
|
cliTest(
|
|
"Hermes uninstall detaches an aliased table before deleting REA",
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-hermes-aliased-uninstall-");
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
await mkdir(profile);
|
|
const run = (arguments_: readonly string[]) =>
|
|
cli.run({
|
|
arguments: arguments_,
|
|
cwd: home,
|
|
environment: {
|
|
USERPROFILE: home,
|
|
HERMES_HOME: profile,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
const setup = await run([
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--skill=false",
|
|
"--yes",
|
|
"--json",
|
|
]);
|
|
expect(setup.exitCode).toBe(0);
|
|
const entry = parse(await readFile(config, "utf8")).mcp_servers.rea;
|
|
const defaults = { rea: entry, other: { command: "other-server" } };
|
|
const original =
|
|
"server_defaults: &servers\n" +
|
|
stringify(defaults)
|
|
.split("\n")
|
|
.filter((line) => line.length > 0)
|
|
.map((line) => ` ${line}\n`)
|
|
.join("") +
|
|
"mcp_servers: *servers\nobserver: *servers\n";
|
|
await writeFile(config, original);
|
|
expect(
|
|
(await run(["doctor", "--client", "hermes", "--json"])).exitCode,
|
|
).toBe(0);
|
|
const removed = await run(["uninstall", "--json"]);
|
|
expect(removed.exitCode).toBe(0);
|
|
const remaining = parse(await readFile(config, "utf8"));
|
|
expect(remaining.mcp_servers).toEqual({ other: defaults.other });
|
|
expect(remaining.server_defaults).toEqual(defaults);
|
|
expect(remaining.observer).toEqual(defaults);
|
|
const configured = await readFile(config, "utf8");
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
|
expect((await run(["uninstall", "--json"])).exitCode).toBe(0);
|
|
expect(await readFile(config, "utf8")).toBe(configured);
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
|
},
|
|
);
|
|
|
|
for (const original of [
|
|
"mcp_servers: *missing\n",
|
|
"server_defaults: &servers invalid\nmcp_servers: *servers\n",
|
|
]) {
|
|
cliTest(
|
|
`Hermes refuses an invalid aliased table: ${original.trim()}`,
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-hermes-invalid-alias-");
|
|
const profile = join(home, "profile");
|
|
const config = join(profile, "config.yaml");
|
|
await mkdir(profile);
|
|
await writeFile(config, original);
|
|
const rejected = await cli.run({
|
|
arguments: [
|
|
"setup",
|
|
"--client",
|
|
"hermes",
|
|
"--skill=false",
|
|
"--yes",
|
|
"--json",
|
|
],
|
|
cwd: home,
|
|
environment: {
|
|
USERPROFILE: home,
|
|
HERMES_HOME: profile,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
},
|
|
timeoutMs: 20_000,
|
|
});
|
|
expect(rejected.exitCode).not.toBe(0);
|
|
expect(rejected.json).toMatchObject({
|
|
status: "needs_human",
|
|
plannedActions: [],
|
|
appliedActions: [],
|
|
});
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
await expect(access(`${config}.rea.backup`)).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
},
|
|
);
|
|
}
|