mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 06:46:10 +00:00
* fix(ci): retire unused internal analysis declarations * test: use an admitted app bundle for provider routing (cherry picked from commit00aa17f12a) * test: keep bundle routing coverage outside oversized group (cherry picked from commit8658353b0d) * fix(ci): align regression fixtures with current admission contracts * test: bind extraction close fixtures to their ZIP digest (cherry picked from commit626d0250db) * test(cli): match typed analysis outcomes in function detail fixture * test(ghidra): preserve empty Java property separator framing * test(electron): retain unknown IPC ownership for native module gaps * fix(electron): share source-scoped call path identity * test(captures): assert safe source identity on SAZ refusal * test(apple): match root occurrence kind to inventory manifest * fix(examples): align directory root occurrence identity * test(electron): align equivalent fixture assertions with upstream * test(process): assert retained capture root identity * test(windows): declare the PE fixture optional header * test(ci): refresh owned registration and verifier fixtures * test(setup): use owned registrations for refresh fixtures * test(process): control unavailable ownership in CLI cleanup fixture * test(process): guard controlled compiler output arguments * test(setup): align equivalent entrypoint guards with upstream * test(process): release the cleanup target through an owned gate * test(ci): align semantic identities and owned fixture facts * test(pcap): retain the upstream DNS wire fixture branch * test(pcap): use the canonical wire ownership fixture * test(pcap): assert exact DNS wire query labels * test(ci): preserve admission and ownership fixture prerequisites * test(ci): align authenticated fixture and cleanup boundaries * test(windows): align native artifact fixtures with upstream * test(android): request complete evidence for inline MCP contracts * test: avoid pinning diagnostic wording * test: verify cleanup diagnostics at their source * test: strengthen evidence and cleanup checks * test: avoid pinning registration guidance --------- Co-authored-by: Plastic-time <Plastic-time@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
316 lines
11 KiB
TypeScript
316 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);
|
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
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 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,
|
|
},
|
|
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,
|
|
});
|
|
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",
|
|
});
|
|
},
|
|
);
|
|
}
|