test(cli): reduce repeated startup and filesystem setup

This commit is contained in:
morluto 2026-10-08 03:59:24 +08:00
parent 9c3479ed95
commit d88d6c1897
19 changed files with 409 additions and 677 deletions

View File

@ -4,8 +4,6 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { createCli } from "./cli.js";
import { createSystemDoctorHost } from "./doctorRuntime.js";
import { captureProcessScenarioFile } from "./application/process/ProcessCli.js";
import { runCapabilityStatus } from "./composition/directAnalysis.js";
import { probeProcessCaptureCapability } from "./process/capture/ProcessHarness.js";
@ -13,17 +11,6 @@ import { parseEvidence } from "./domain/evidence.js";
import { parseProcessCapture } from "./domain/process/processCapture.js";
describe("the CLI takes its environment as an input", () => {
it("builds from an explicitly supplied environment", () => {
expect(createCli({ REA_LOG_LEVEL: "debug" })).toBeDefined();
expect(createCli({})).toBeDefined();
});
it("still builds with no environment supplied", () => {
// The default keeps production behaviour identical.
expect(createCli()).toBeDefined();
expect(createSystemDoctorHost()).toBeDefined();
});
it("resolves from injected PATH and inherits injected env with scenario overrides", async ({
skip,
}) => {

View File

@ -1,7 +1,5 @@
import { describe, expect, it } from "vitest";
import { browserContext } from "./cliBrowserContext.js";
import { CdpBrowserProvider } from "./browser/CdpBrowserProvider.js";
import { HopperProvider } from "./hopper/HopperProvider.js";
import { silentLogger } from "./logger.js";
import { parseConfig } from "./config.js";
@ -35,9 +33,3 @@ describe("host platform is injected, not read from the ambient process", () => {
},
);
});
describe("browser CLI provider setup", () => {
it("does not require permission policy configuration", () => {
expect(browserContext().provider).toBeInstanceOf(CdpBrowserProvider);
});
});

View File

@ -1,12 +0,0 @@
import { describe, expect, it } from "vitest";
import { electronPageInspectionOptions } from "./cliObservationOptions.js";
describe("Electron CLI observation duration", () => {
it("accepts caller-selected windows longer than ten seconds", () => {
expect(
electronPageInspectionOptions.parse({ observationMs: 60_000 })
.observationMs,
).toBe(60_000);
});
});

View File

@ -12,263 +12,156 @@ import {
import { importEvidenceBundleInputSchema } from "./sessionToolSchemas.js";
import { binarySessionInputSchema } from "./sessionStatusContract.js";
const openBinaryAbsolute = (snapshot_path: string) =>
openBinaryInputSchema.safeParse({ path: "/tmp/fixture", snapshot_path });
type PathCase = {
readonly name: string;
readonly accepts: (value: unknown) => boolean;
readonly relative: unknown[];
readonly absolute: string;
readonly omitted?: () => boolean;
};
describe("session snapshot path inputs require absolute local paths", () => {
it("rejects a relative snapshot path when opening a binary", () => {
expect(openBinaryAbsolute("relative/analysis.json").success).toBe(false);
});
it("rejects a parent-relative snapshot path when opening a binary", () => {
expect(openBinaryAbsolute("../outside/analysis.json").success).toBe(false);
});
it("accepts an absolute snapshot path when opening a binary", () => {
expect(openBinaryAbsolute("/tmp/rea/analysis.json").success).toBe(true);
});
it("still accepts open_binary without a snapshot path", () => {
expect(
const cases: readonly PathCase[] = [
{
name: "open binary snapshot",
accepts: (snapshot_path) =>
openBinaryInputSchema.safeParse({ path: "/tmp/fixture", snapshot_path })
.success,
relative: ["relative/analysis.json", "../outside/analysis.json"],
absolute: "/tmp/rea/analysis.json",
omitted: () =>
openBinaryInputSchema.safeParse({ path: "/tmp/fixture" }).success,
).toBe(true);
});
it("rejects a relative snapshot path when closing a binary", () => {
expect(
closeBinaryInputSchema.safeParse({ snapshot_path: "analysis.json" })
.success,
).toBe(false);
});
it("accepts an absolute snapshot path when closing a binary", () => {
expect(
closeBinaryInputSchema.safeParse({ snapshot_path: "/tmp/analysis.json" })
.success,
).toBe(true);
});
it("still accepts close_binary without a snapshot path", () => {
expect(closeBinaryInputSchema.safeParse({}).success).toBe(true);
});
});
describe("evidence bundle export path requires an absolute local path", () => {
it("rejects a relative export path", () => {
expect(
exportEvidenceBundleInputSchema.safeParse({ path: "out/bundle.json" })
.success,
).toBe(false);
});
it("rejects a parent-relative export path", () => {
expect(
exportEvidenceBundleInputSchema.safeParse({
path: "../../bundle.json",
},
{
name: "close binary snapshot",
accepts: (snapshot_path) =>
closeBinaryInputSchema.safeParse({ snapshot_path }).success,
relative: ["analysis.json"],
absolute: "/tmp/analysis.json",
omitted: () => closeBinaryInputSchema.safeParse({}).success,
},
{
name: "evidence bundle export",
accepts: (path) =>
exportEvidenceBundleInputSchema.safeParse({ path }).success,
relative: ["out/bundle.json", "../../bundle.json"],
absolute: "/tmp/bundle.json",
},
{
name: "managed target",
accepts: (path) => managedArtifactInputSchema.safeParse({ path }).success,
relative: ["bin/app.dll"],
absolute: "/tmp/app.dll",
omitted: () => managedArtifactInputSchema.safeParse({}).success,
},
{
name: "open binary target",
accepts: (path) => openBinaryInputSchema.safeParse({ path }).success,
relative: ["fixtures/app.bin"],
absolute: "/tmp/fixture.bin",
},
{
name: "evidence bundle import",
accepts: (path) =>
importEvidenceBundleInputSchema.safeParse({ path }).success,
relative: ["evidence.json"],
absolute: "/tmp/evidence.json",
},
{
name: "firmware inspection",
accepts: (path) =>
firmwareInputSchemas.inspect_firmware_regions.safeParse({ path }).success,
relative: ["firmware.bin"],
absolute: "/tmp/firmware.bin",
},
{
name: "firmware extraction",
accepts: (path) =>
firmwareInputSchemas.extract_firmware.safeParse({
path,
output_directory: "/tmp/firmware-output",
}).success,
).toBe(false);
});
relative: ["firmware.bin"],
absolute: "/tmp/firmware.bin",
},
{
name: "browser executable",
accepts: (executable_path) =>
browserScenarioBrowserSchema.safeParse({
mode: "launch",
executable_path,
}).success,
relative: ["chrome"],
absolute: "/opt/chromium/chrome",
},
{
name: "Electron executable",
accepts: (executable_path) =>
electronActiveObservationInputSchema.safeParse({
executable_path,
application_path: "/tmp/electron-app/main.js",
}).success,
relative: ["Electron"],
absolute: "/Applications/Electron.app/Contents/MacOS/Electron",
},
{
name: "Electron application",
accepts: (application_path) =>
electronActiveObservationInputSchema.safeParse({
executable_path: "/Applications/Electron.app/Contents/MacOS/Electron",
application_path,
}).success,
relative: ["main.js"],
absolute: "/tmp/electron-app/main.js",
},
{
name: "Electron application root",
accepts: (application_root) =>
electronActiveObservationInputSchema.safeParse({
executable_path: "/Applications/Electron.app/Contents/MacOS/Electron",
application_path: "/tmp/electron-app/main.js",
application_root,
}).success,
relative: ["app"],
absolute: "/tmp/electron-app",
omitted: () =>
electronActiveObservationInputSchema.safeParse({
executable_path: "/Applications/Electron.app/Contents/MacOS/Electron",
application_path: "/tmp/electron-app/main.js",
}).success,
},
{
name: "expected server",
accepts: (expected_server_path) =>
binarySessionInputSchema.safeParse({ expected_server_path }).success,
relative: ["dist/main.js"],
absolute: "/opt/rea/dist/main.js",
omitted: () => binarySessionInputSchema.safeParse({}).success,
},
];
it("accepts an absolute export path and keeps overwrite semantics", () => {
expect(
exportEvidenceBundleInputSchema.safeParse({
path: "/tmp/bundle.json",
}),
).toMatchObject({ success: true, data: { overwrite: false } });
});
it.each(cases)("$name requires an absolute local path", (pathCase) => {
for (const relative of pathCase.relative)
expect(pathCase.accepts(relative), pathCase.name).toBe(false);
expect(pathCase.accepts(pathCase.absolute), pathCase.name).toBe(true);
if (pathCase.omitted !== undefined)
expect(pathCase.omitted(), pathCase.name).toBe(true);
});
describe("managed target path requires an absolute local path", () => {
it("rejects a relative managed target path", () => {
expect(
managedArtifactInputSchema.safeParse({ path: "bin/app.dll" }).success,
).toBe(false);
});
it("accepts an absolute managed target path", () => {
expect(
managedArtifactInputSchema.safeParse({ path: "/tmp/app.dll" }).success,
).toBe(true);
});
it("still accepts omitting the path to reuse the active target", () => {
expect(managedArtifactInputSchema.safeParse({}).success).toBe(true);
});
it("keeps evidence bundle export overwrite disabled by default", () => {
expect(
exportEvidenceBundleInputSchema.safeParse({ path: "/tmp/bundle.json" }),
).toMatchObject({ success: true, data: { overwrite: false } });
});
describe.runIf(process.platform === "win32")(
"windows absolute path forms on windows hosts",
"Windows absolute path forms",
() => {
it("accepts drive-letter backslash snapshot paths", () => {
expect(
closeBinaryInputSchema.safeParse({
snapshot_path: "C:\\rea\\analysis.json",
}).success,
).toBe(true);
});
it("accepts drive-letter forward-slash snapshot paths", () => {
expect(
closeBinaryInputSchema.safeParse({
snapshot_path: "C:/rea/analysis.json",
}).success,
).toBe(true);
});
it("accepts forward-slash export paths", () => {
expect(
exportEvidenceBundleInputSchema.safeParse({
path: "C:/rea/bundle.json",
}).success,
).toBe(true);
it.each([
[closeBinaryInputSchema, { snapshot_path: "C:\\rea\\analysis.json" }],
[closeBinaryInputSchema, { snapshot_path: "C:/rea/analysis.json" }],
[exportEvidenceBundleInputSchema, { path: "C:/rea/bundle.json" }],
])("accepts %o", (schema, input) => {
expect(schema.safeParse(input).success).toBe(true);
});
},
);
describe("open binary target path requires an absolute local path", () => {
it("rejects a relative target path", () => {
expect(
openBinaryInputSchema.safeParse({ path: "fixtures/app.bin" }).success,
).toBe(false);
});
it("accepts an absolute target path", () => {
expect(
openBinaryInputSchema.safeParse({ path: "/tmp/fixture.bin" }).success,
).toBe(true);
});
});
describe("evidence bundle import path requires an absolute local path", () => {
it("rejects a relative bundle path", () => {
expect(
importEvidenceBundleInputSchema.safeParse({ path: "evidence.json" })
.success,
).toBe(false);
});
it("accepts an absolute bundle path", () => {
expect(
importEvidenceBundleInputSchema.safeParse({ path: "/tmp/evidence.json" })
.success,
).toBe(true);
});
});
describe("firmware input path requires an absolute local path", () => {
it("rejects a relative firmware path when inspecting regions", () => {
expect(
firmwareInputSchemas.inspect_firmware_regions.safeParse({
path: "firmware.bin",
}).success,
).toBe(false);
});
it("rejects a relative firmware path when extracting", () => {
expect(
firmwareInputSchemas.extract_firmware.safeParse({
path: "firmware.bin",
output_directory: "/tmp/firmware-output",
}).success,
).toBe(false);
});
it("accepts an absolute firmware path", () => {
expect(
firmwareInputSchemas.inspect_firmware_regions.safeParse({
path: "/tmp/firmware.bin",
}).success,
).toBe(true);
});
});
describe("browser executable path requires an absolute local path", () => {
it("rejects a bare executable name", () => {
expect(
browserScenarioBrowserSchema.safeParse({
mode: "launch",
executable_path: "chrome",
}).success,
).toBe(false);
});
it("accepts an absolute executable path", () => {
expect(
browserScenarioBrowserSchema.safeParse({
mode: "launch",
executable_path: "/opt/chromium/chrome",
}).success,
).toBe(true);
});
});
describe("electron observation paths require absolute local paths", () => {
const valid = {
executable_path: "/Applications/Electron.app/Contents/MacOS/Electron",
application_path: "/tmp/electron-app/main.js",
};
it("rejects a relative executable path", () => {
expect(
electronActiveObservationInputSchema.safeParse({
...valid,
executable_path: "Electron",
}).success,
).toBe(false);
});
it("rejects a relative application path", () => {
expect(
electronActiveObservationInputSchema.safeParse({
...valid,
application_path: "main.js",
}).success,
).toBe(false);
});
it("rejects a relative application root", () => {
expect(
electronActiveObservationInputSchema.safeParse({
...valid,
application_root: "app",
}).success,
).toBe(false);
});
it("accepts absolute observation paths", () => {
expect(
electronActiveObservationInputSchema.safeParse({
...valid,
application_root: "/tmp/electron-app",
}).success,
).toBe(true);
});
it("still accepts omitting the application root", () => {
expect(electronActiveObservationInputSchema.safeParse(valid).success).toBe(
true,
);
});
});
describe("expected server path requires an absolute local path", () => {
it("rejects a relative server path", () => {
expect(
binarySessionInputSchema.safeParse({
expected_server_path: "dist/main.js",
}).success,
).toBe(false);
});
it("accepts an absolute server path", () => {
expect(
binarySessionInputSchema.safeParse({
expected_server_path: "/opt/rea/dist/main.js",
}).success,
).toBe(true);
});
it("still accepts omitting the server path", () => {
expect(binarySessionInputSchema.safeParse({}).success).toBe(true);
});
});

View File

@ -3,7 +3,6 @@ import { join } from "node:path";
import { describe, expect } from "vitest";
import { parseCliJsonInput } from "../../../src/cliJsonInput.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
@ -30,46 +29,63 @@ const JSON_COMMANDS = [
describe("compiled CLI JSON input failure status", () => {
for (const [command, operation] of JSON_COMMANDS)
for (const kind of ["inline", "file", "missing"] as const)
cliTest(
`${command} rejects malformed or unreadable ${kind} input before dispatch`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-cli-json-status-");
const input = kind === "inline" ? "{" : join(root, "input.json");
if (kind === "file") await writeFile(input, "{");
const parsed = await parseCliJsonInput(input, operation);
if (parsed.ok)
throw new Error("Expected JSON input to fail before dispatch");
cliTest(
`${command} rejects malformed inline input before dispatch`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-cli-json-status-");
const input = "{";
const result = await cli.run({
arguments: [command, input, "--json"],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
const result = await cli.run({
arguments: [command, input, "--json"],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
expect(result.json).toMatchObject({
code: "invalid_request",
category: "invalid_input",
details: { operation },
});
expect(result.stderr).toBe("");
expect(result.exitCode).toBe(1);
},
);
expect(result.stdout).toBe(
`${JSON.stringify(parsed.error, null, 2)}\n`,
);
expect(result.json).toMatchObject({
code: "invalid_request",
category: "invalid_input",
...(kind === "missing" ? {} : { details: { operation } }),
...(kind === "inline"
? {}
: {
input_path: input,
input_reason:
kind === "file" ? "invalid-json" : "read-failed",
}),
});
expect(result.stderr).toBe("");
expect(result.exitCode).toBe(1);
},
);
for (const kind of ["file", "missing"] as const)
cliTest(
`preserves ${kind} JSON input diagnostics through a real command`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-cli-json-status-");
const input = join(root, "input.json");
if (kind === "file") await writeFile(input, "{");
const result = await cli.run({
arguments: ["compare-javascript-export-shapes", input, "--json"],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
expect(result.json).toMatchObject({
code: "invalid_request",
category: "invalid_input",
input_path: input,
input_reason: kind === "file" ? "invalid-json" : "read-failed",
...(kind === "file"
? { details: { operation: "compare-javascript-export-shapes" } }
: {}),
});
expect(result.stderr).toBe("");
expect(result.exitCode).toBe(1);
},
);
});
describe("compiled CLI JSON input diagnostic preservation", () => {
for (const [format, flags] of [
["JSON", ["--json"]],
["JSONL", ["--format", "jsonl"]],

View File

@ -31,10 +31,7 @@ const observe = async (path: string) => {
describe("artifact comparison summary", () => {
it.each([
{ removed: 1, added: 1 },
{ removed: 1, added: 2 },
{ removed: 2, added: 1 },
{ removed: 0, added: 1 },
{ removed: 1, added: 0 },
{ removed: 0, added: 0 },
])(
"counts unchanged paths with $removed removals and $added additions",
@ -74,15 +71,6 @@ describe("artifact comparison summary", () => {
contradiction: 0,
});
expect(comparison.changes).toHaveLength(removed + added + changed);
expect(comparison.summary.unchanged + comparison.changes.length).toBe(
3 + removed + added,
);
expect(
comparison.changes.map(({ logical_path }) => logical_path),
).not.toContain("same.txt");
expect(
comparison.changes.map(({ logical_path }) => logical_path),
).not.toContain("duplicate.txt");
},
);
});

View File

@ -14,10 +14,6 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const permutations = [
["pkg/", "pkg/sub/", "pkg/sub/data.txt"],
["pkg/", "pkg/sub/data.txt", "pkg/sub/"],
["pkg/sub/", "pkg/", "pkg/sub/data.txt"],
["pkg/sub/", "pkg/sub/data.txt", "pkg/"],
["pkg/sub/data.txt", "pkg/", "pkg/sub/"],
["pkg/sub/data.txt", "pkg/sub/", "pkg/"],
];
const expectedParents = {

View File

@ -7,7 +7,7 @@ import { readBoundedFileBytes } from "../../../src/process/BoundedFileBytes.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("bounded file byte reads", () => {
it.each([0, 1, 65_536, 65_537])(
it.each([0, 65_536])(
"accepts a file exactly at the %i byte limit",
async (limit) => {
const root = await createTestTempDirectory("rea-bounded-bytes-");

View File

@ -1,21 +1,14 @@
import { writeFile } from "node:fs/promises";
import { isAbsolute, join, resolve } from "node:path";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
parseCliJsonInput,
resolveCliJsonPaths,
} from "../../../src/cliJsonInput.js";
import { parseCliJsonInput } from "../../../src/cliJsonInput.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("CLI JSON input", () => {
it("preserves JSON scalars and distinguishes malformed inline text from files", async () => {
for (const value of [null, false, 0, "text", [], {}])
expect(
await parseCliJsonInput(JSON.stringify(value), "test-input"),
).toEqual({ ok: true, value });
expect(await parseCliJsonInput("{", "test-input")).toMatchObject({
it("distinguishes malformed inline text from files and preserves bracket-prefixed paths", async () => {
expect(await parseCliJsonInput("[", "test-input")).toMatchObject({
ok: false,
error: {
details: { issues: [{ reason: "invalid_format", expected: "JSON" }] },
@ -34,6 +27,10 @@ describe("CLI JSON input", () => {
ok: true,
value: { value: 1 },
});
expect(await parseCliJsonInput("false", "test-input")).toEqual({
ok: true,
value: false,
});
const bracketPath = join(root, "[input].json");
await writeFile(bracketPath, '["preserved"]');
expect(await parseCliJsonInput(bracketPath, "test-input")).toEqual({
@ -54,41 +51,3 @@ describe("CLI JSON input", () => {
});
});
});
describe("resolveCliJsonPaths", () => {
it("resolves named relative fields against the operator working directory", () => {
expect(
resolveCliJsonPaths({ executable_path: "chrome" }, [["executable_path"]]),
).toEqual({ executable_path: resolve("chrome") });
expect(isAbsolute(resolve("chrome"))).toBe(true);
});
it("leaves absolute fields unchanged", () => {
const value = { path: "/tmp/evidence.json" };
expect(resolveCliJsonPaths(value, [["path"]])).toEqual(value);
});
it("resolves nested key paths and preserves sibling fields", () => {
expect(
resolveCliJsonPaths(
{
browser: { mode: "launch", executable_path: "chrome" },
actions: [],
},
[["browser", "executable_path"]],
),
).toEqual({
browser: { mode: "launch", executable_path: resolve("chrome") },
actions: [],
});
});
it("passes through non-objects, missing keys, and non-string fields", () => {
expect(resolveCliJsonPaths(null, [["path"]])).toBe(null);
expect(resolveCliJsonPaths("text", [["path"]])).toBe("text");
expect(resolveCliJsonPaths({ other: 1 }, [["path"]])).toEqual({
other: 1,
});
expect(resolveCliJsonPaths({ path: 42 }, [["path"]])).toEqual({ path: 42 });
});
});

View File

@ -15,7 +15,6 @@ import {
inspectClientConfiguration,
} from "../../../src/application/SetupClientConfiguration.js";
import { supportedClients } from "../../../src/application/SupportedClients.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe.each([
@ -46,68 +45,55 @@ describe.each([
await mkdir(dirname(client.configPath), { recursive: true });
await writeFile(client.configPath, original);
const currentCommandPath = join(home, "rea");
for (const command of [
["npx", "-y", PRODUCT_IDENTITY.registrationPackageSpecifier, "mcp"],
[currentCommandPath, "mcp"],
]) {
expect(await inspectClientConfiguration(client, {}, command)).toEqual({
status: "update",
backupPath: `${client.configPath}.rea.backup`,
});
expect(await clientConfigurationAligned(client, {}, command)).toBe(
false,
);
expect(await configureClientConfiguration(client, {}, command)).toEqual(
{
status: "configured",
backupPath: `${client.configPath}.rea.backup`,
},
);
const updated = await readFile(client.configPath, "utf8");
expect(
updated.startsWith(`${bom}// Keep the client preferences.\r\n`),
).toBe(true);
expect(
parseClientConfiguration(updated, client.format).document,
).toEqual({
const command = [currentCommandPath, "mcp"];
expect(await inspectClientConfiguration(client, {}, command)).toEqual({
status: "update",
backupPath: `${client.configPath}.rea.backup`,
});
expect(await clientConfigurationAligned(client, {}, command)).toBe(false);
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "configured",
backupPath: `${client.configPath}.rea.backup`,
});
const updated = await readFile(client.configPath, "utf8");
expect(
updated.startsWith(`${bom}// Keep the client preferences.\r\n`),
).toBe(true);
expect(parseClientConfiguration(updated, client.format).document).toEqual(
{
label: "left\uFEFFright",
[serversKey]: {
other: { command: "other" },
rea: clientRegistrationEntry(client.format, command, {}),
},
theme: "dark",
});
expect(await clientConfigurationAligned(client, {}, command)).toBe(
true,
);
expect(await inspectClientConfiguration(client, {}, command)).toEqual({
status: "already_current",
});
expect(
await readClientRegistrationStatuses(home, currentCommandPath, {
platform: "linux",
environment: {},
}),
).toEqual([
{
client: name,
config_path: client.configPath,
state: "aligned",
command,
remediation: null,
},
]);
expect(await configureClientConfiguration(client, {}, command)).toEqual(
{
status: "unchanged",
},
);
expect(await readFile(client.configPath)).toEqual(Buffer.from(updated));
expect(await readFile(`${client.configPath}.rea.backup`)).toEqual(
Buffer.from(original),
);
}
},
);
expect(await clientConfigurationAligned(client, {}, command)).toBe(true);
expect(await inspectClientConfiguration(client, {}, command)).toEqual({
status: "already_current",
});
expect(
await readClientRegistrationStatuses(home, currentCommandPath, {
platform: "linux",
environment: {},
}),
).toEqual([
{
client: name,
config_path: client.configPath,
state: "aligned",
command,
remediation: null,
},
]);
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
expect(await readFile(client.configPath)).toEqual(Buffer.from(updated));
expect(await readFile(`${client.configPath}.rea.backup`)).toEqual(
Buffer.from(original),
);
},
);
});

View File

@ -34,25 +34,23 @@ describe("client configuration write failures", () => {
});
describe("TOML client configuration comparison", () => {
it.each(["nan", "inf", "-inf"])(
"replaces a registration containing %s and preserves unrelated settings",
async (value) => {
const root = await createTestTempDirectory("rea-client-toml-");
const configPath = join(root, "config.toml");
const original = `unrelated = ${value}\n[mcp_servers.rea]\ncommand = "old"\nstartup_timeout_sec = ${value}\n`;
await writeFile(configPath, original);
const client = { name: "codex", format: "toml", configPath } as const;
expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({
status: "configured",
backupPath: `${configPath}.rea.backup`,
});
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
expect(await readFile(configPath, "utf8")).toContain(
`unrelated = ${value}`,
);
expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({
status: "unchanged",
});
},
);
it("replaces a registration containing a non-finite TOML float and preserves unrelated settings", async () => {
const value = "-inf";
const root = await createTestTempDirectory("rea-client-toml-");
const configPath = join(root, "config.toml");
const original = `unrelated = ${value}\n[mcp_servers.rea]\ncommand = "old"\nstartup_timeout_sec = ${value}\n`;
await writeFile(configPath, original);
const client = { name: "codex", format: "toml", configPath } as const;
expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({
status: "configured",
backupPath: `${configPath}.rea.backup`,
});
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
expect(await readFile(configPath, "utf8")).toContain(
`unrelated = ${value}`,
);
expect(await configureTomlClient(client, {}, ["rea", "mcp"])).toEqual({
status: "unchanged",
});
});
});

View File

@ -6,36 +6,33 @@ import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("DOS COM admission", () => {
it.each(["fixture.com", "fixture.hop", "fixture.js", "fixture.bin"])(
"requires explicit interpretation for %s",
async (name) => {
const directory = await createTestTempDirectory("rea-com-target-");
const path = join(directory, name);
const bytes = Buffer.from("b83412c3", "hex");
await writeFile(path, bytes);
const target = await parseBinaryTarget(
it("requires explicit interpretation even when a DOS COM uses a misleading suffix", async () => {
const directory = await createTestTempDirectory("rea-com-target-");
const path = join(directory, "fixture.js");
const bytes = Buffer.from("b83412c3", "hex");
await writeFile(path, bytes);
const target = await parseBinaryTarget(
path,
directory,
"arm64",
undefined,
"dos-com",
);
expect(target).toMatchObject({
ok: true,
value: {
path,
directory,
"arm64",
undefined,
"dos-com",
);
expect(target).toMatchObject({
ok: true,
value: {
path,
format: "dos-com",
kind: "executable",
architecture: "x86",
availableArchitectures: ["x86"],
sha256: createHash("sha256").update(bytes).digest("hex"),
},
});
expect(await readFile(path)).toEqual(bytes);
const detected = await parseBinaryTarget(path);
expect(detected.ok && detected.value.format).not.toBe("dos-com");
},
);
format: "dos-com",
kind: "executable",
architecture: "x86",
availableArchitectures: ["x86"],
sha256: createHash("sha256").update(bytes).digest("hex"),
},
});
expect(await readFile(path)).toEqual(bytes);
const detected = await parseBinaryTarget(path);
expect(detected.ok && detected.value.format).not.toBe("dos-com");
});
it.each([0, 0xff01])("rejects invalid file extent %i", async (length) => {
const directory = await createTestTempDirectory("rea-com-target-");
const path = join(directory, "fixture.com");

View File

@ -30,18 +30,22 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => {
expect(await readdir(fixture.temporary)).toEqual([]);
});
it.each(["22.19.0", "24.11.0", "26.0.0", "27.0.0"])(
it.each(["22.19.0", "24.11.0", "26.0.0"])(
"accepts supported Node %s without installing or replacing it",
async (version) => {
const fixture = await createFixture();
const result = await runInstaller(fixture, ["--version", "0.3.0"], {
FAKE_NODE_VERSION: version,
});
const result = await runInstaller(
fixture,
["--version", "0.3.0", "--dry-run"],
{
FAKE_NODE_VERSION: version,
},
);
expect(result.stdout).toContain(`Runtime: Node.js ${version}`);
},
);
it.each(["22.18.9", "23.0.0", "24.10.9", "25.1.0", "26.0.0-rc.1"])(
it.each(["22.18.9", "24.10.9", "23.0.0", "26.0.0-rc.1"])(
"rejects unsupported Node %s before invoking npm",
async (version) => {
const fixture = await createFixture();
@ -77,15 +81,7 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => {
});
});
// Every installer failure must carry a recovery action. AGENTS.md requires
// actionable diagnostics, including for mismatch locations, so a failure that
// only states what went wrong is a regression.
// The installer is the one surface whose output is read directly by a person
// in a terminal, so the exact wording is a real contract and stays pinned.
// Alongside it, each row asserts the two properties that matter
// independently of phrasing: the failure is announced as a REA installation
// failure, and the message carries a recovery action. A copy edit then shows
// up as one reviewable diff instead of silently losing the guidance.
// Keep each distinct failure route while checking its reason and recovery.
it.each([
[
"unsupported Node",
@ -158,11 +154,6 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => {
expect(failure).toBeInstanceOf(Error);
const stderr = String((failure as { stderr?: unknown }).stderr ?? "");
expect(stderr).toBe(message);
expect(stderr.startsWith("REA installation failed: ")).toBe(true);
// Actionable guidance must survive any rewording.
expect(stderr).toMatch(
/\b(?:Check|Repair|Reinstall|Install|Retry|then retry|use Node)\b/,
);
expect(await readdir(fixture.temporary)).toEqual([]);
},
);
@ -182,7 +173,7 @@ describe("curl installer scenarios", { timeout: 20_000 }, () => {
});
describe("installer semantic version parsing", { timeout: 20_000 }, () => {
it.each(["01.2.3", "1.2.3-01", "1.2.3-alpha..1"])(
it.each(["1.2.3-01"])(
"rejects malformed semantic versions before invoking npm: %s",
async (version) => {
const fixture = await createFixture();
@ -300,19 +291,20 @@ const createFixture = async (): Promise<InstallerFixture> => {
const npmLog = join(root, "npm.log");
const reaLog = join(root, "rea.log");
await Promise.all([mkdir(home), mkdir(bin), mkdir(temporary)]);
await executable(
join(bin, "uname"),
'#!/bin/sh\n[ "$1" = "-m" ] && echo x86_64 || printf \'%s\\n\' "${FAKE_PLATFORM:-Linux}"\n',
);
await executable(
join(bin, "node"),
`#!/bin/sh
await Promise.all([
executable(
join(bin, "uname"),
'#!/bin/sh\n[ "$1" = "-m" ] && echo x86_64 || printf \'%s\\n\' "${FAKE_PLATFORM:-Linux}"\n',
),
executable(
join(bin, "node"),
`#!/bin/sh
if [ "$1" = "-p" ]; then printf '%s\n' "\${FAKE_NODE_VERSION:-24.18.0}"; else exec ${shellQuote(process.execPath)} "$@"; fi
`,
);
await executable(
join(bin, "npm"),
`#!/bin/sh
),
executable(
join(bin, "npm"),
`#!/bin/sh
printf '%s\n' "$*" >> "$FAKE_NPM_LOG"
[ "$1" = "prefix" ] && { [ "\${FAKE_NPM_PREFIX_FAIL:-}" = "1" ] && exit 1; printf '%s\n' "$FAKE_NPM_PREFIX"; exit 0; }
[ "\${FAKE_NPM_FAIL:-}" = "1" ] && exit 1
@ -326,25 +318,27 @@ mkdir -p "$prefix/bin"
cp "$FAKE_REA_SOURCE" "$prefix/bin/rea"
chmod +x "$prefix/bin/rea"
`,
);
await executable(
join(bin, "curl"),
`#!/bin/sh
),
executable(
join(bin, "curl"),
`#!/bin/sh
[ "\${FAKE_CURL_FAIL:-}" = "1" ] && exit 1
if [ -n "\${FAKE_CURL_BODY:-}" ]; then printf '%s' "$FAKE_CURL_BODY"; else printf '%s' '{"tag_name":"rea-agents-0.3.0"}'; fi
`,
);
await executable(
join(bin, "rea-source"),
`#!/bin/sh
),
executable(
join(bin, "rea-source"),
`#!/bin/sh
printf '%s\n' "$*" >> "$FAKE_REA_LOG"
[ "\${FAKE_REA_VERSION_FAIL:-}" = "1" ] && exit 1
[ "$1" = "--version" ] && printf '%s\n' "\${FAKE_REA_VERSION:-0.3.0}"
exit 0
`,
);
for (const command of ["chmod", "cp", "mkdir", "tr"])
await symlink(`/usr/bin/${command}`, join(bin, command));
),
...["chmod", "cp", "mkdir", "tr"].map((command) =>
symlink(`/usr/bin/${command}`, join(bin, command)),
),
]);
return { home, bin, temporary, npmLog, reaLog };
};

View File

@ -5,31 +5,28 @@ import { readJsonFile } from "../../../src/application/JsonFiles.js";
import { parseCliJsonInput } from "../../../src/cliJsonInput.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("JSON file UTF-8 byte integrity", () => {
it.each([0x80, 0xe9, 0xff])(
"rejects invalid UTF-8 byte %s through both file input boundaries",
async (invalidByte) => {
const root = await createTestTempDirectory("rea-json-utf8-");
const path = join(root, "input.json");
await writeFile(
path,
Buffer.concat([
Buffer.from('{"message":"'),
Buffer.from([invalidByte]),
Buffer.from('"}'),
]),
);
const fileInput = await readJsonFile(path);
const cliInput = await parseCliJsonInput(path, "compare_web_captures");
expect(fileInput).toMatchObject({
ok: false,
error: { reason: "invalid-json" },
});
expect(cliInput).toMatchObject({
ok: false,
error: { input_reason: "invalid-json" },
});
},
);
it("rejects invalid UTF-8 through both file input boundaries", async () => {
const root = await createTestTempDirectory("rea-json-utf8-");
const path = join(root, "input.json");
await writeFile(
path,
Buffer.concat([
Buffer.from('{"message":"'),
Buffer.from([0x80]),
Buffer.from('"}'),
]),
);
const fileInput = await readJsonFile(path);
const cliInput = await parseCliJsonInput(path, "compare_web_captures");
expect(fileInput).toMatchObject({
ok: false,
error: { reason: "invalid-json" },
});
expect(cliInput).toMatchObject({
ok: false,
error: { input_reason: "invalid-json" },
});
});
it("preserves a legitimate UTF-8 replacement character", async () => {
const root = await createTestTempDirectory("rea-json-utf8-control-");
const path = join(root, "input.json");

View File

@ -11,16 +11,12 @@ it("imports test-suffixed filenames with unchanged paths, hashes, and languages"
const root = await createTestTempDirectory("rea-reference-test-filenames-");
const files = [
["main_test.go", "package main\n", ["source", "test"], "Go"],
["main_test.py", "assert True\n", ["source", "test"], "Python"],
["test_helper.py", "assert True\n", ["source", "test"], "Python"],
["parser_spec.rs", "fn checks() {}\n", ["source", "test"], "Rust"],
["Widget_SPEC.TS", "export {};\n", ["source", "test"], "TypeScript"],
["main_test", "test fixture\n", ["test"], null],
["main_spec", "spec fixture\n", ["test"], null],
["test_helper.py", "assert True\n", ["source", "test"], "Python"],
["main.test.js", "export {};\n", ["source", "test"], "JavaScript"],
["main.go", "package main\n", ["source"], "Go"],
["main_test_helper.py", "value = 1\n", ["source"], "Python"],
["main_specimen.rs", "fn main() {}\n", ["source"], "Rust"],
["main_test.py.bak", "backup\n", ["unknown"], null],
] as const;
await Promise.all(

View File

@ -243,36 +243,34 @@ describe("JSON client configuration migration and provider paths", () => {
expect(await readFile(configPath, "utf8")).toBe("not-json");
});
it.each(["null", "[]", '"value"'])(
"refuses a non-object JSON root without overwriting %s",
async (original) => {
const directory = await createTestTempDirectory("rea-setup-");
const configPath = join(directory, "mcp.json");
await writeFile(configPath, original);
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual(
{ status: "failed", reason: "readback" },
);
expect(await readFile(configPath, "utf8")).toBe(original);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
},
);
it("refuses a non-object JSON root without overwriting it", async () => {
const original = "null";
const directory = await createTestTempDirectory("rea-setup-");
const configPath = join(directory, "mcp.json");
await writeFile(configPath, original);
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({
status: "failed",
reason: "readback",
});
expect(await readFile(configPath, "utf8")).toBe(original);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
});
it.each(["null", "[]", '"value"'])(
"preserves a non-object mcpServers value %s",
async (servers) => {
const directory = await createTestTempDirectory("rea-setup-");
const configPath = join(directory, "mcp.json");
const original = `{"mcpServers":${servers}}`;
await writeFile(configPath, original);
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual(
{ status: "failed", reason: "readback" },
);
expect(await readFile(configPath, "utf8")).toBe(original);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
},
);
it("preserves a non-object mcpServers value", async () => {
const servers = "[]";
const directory = await createTestTempDirectory("rea-setup-");
const configPath = join(directory, "mcp.json");
const original = `{"mcpServers":${servers}}`;
await writeFile(configPath, original);
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({
status: "failed",
reason: "readback",
});
expect(await readFile(configPath, "utf8")).toBe(original);
await expect(
readFile(`${configPath}.rea.backup`, "utf8"),
).rejects.toThrow();
});
});

View File

@ -1,36 +0,0 @@
import { access, realpath, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("test temporary directory ownership", { concurrent: false }, () => {
let completedDirectory: string | undefined;
it("creates a canonical directory owned by the current test", async () => {
const directory = await createTestTempDirectory("rea-owned-temp-");
completedDirectory = directory;
expect(directory).toBe(await realpath(directory));
expect(dirname(directory)).toBe(await realpath(tmpdir()));
await writeFile(join(directory, "nested.txt"), "owned");
});
it("removes the prior test directory after that test finishes", async () => {
const directory = completedDirectory;
if (directory === undefined)
throw new Error("Prior test did not create a directory");
await expect(access(directory)).rejects.toMatchObject({
code: "ENOENT",
});
});
it.each(["", "../escape-", "/absolute-", "missing-dash"])(
"rejects unsafe prefix %j",
async (prefix) => {
await expect(createTestTempDirectory(prefix)).rejects.toThrow(TypeError);
},
);
});

View File

@ -9,21 +9,16 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("ZIP signature detection", () => {
it.each([
[0x03, 0x04, true],
[0x05, 0x06, true],
[0x07, 0x08, true],
[0x03, 0x06, false],
[0x03, 0x08, false],
[0x05, 0x04, false],
[0x05, 0x08, false],
[0x07, 0x04, false],
[0x07, 0x06, false],
{ signature: [0x50, 0x4b, 0x05, 0x06], archive: true },
{ signature: [0x50, 0x4b, 0x03, 0x06], archive: false },
{ signature: [0x50, 0x4b, 0x03, 0x04], archive: true },
{ signature: [0x50, 0x4b, 0x07, 0x08], archive: true },
] as const)(
"classifies PK %i %i with archive signature %s",
async (third, fourth, archive) => {
"routes representative ZIP signature $signature through inventory and target parsing",
async ({ signature, archive }) => {
const root = await createTestTempDirectory("rea-zip-signature-");
const path = join(root, "input");
await writeFile(path, Buffer.from([0x50, 0x4b, third, fourth]));
await writeFile(path, Buffer.from(signature));
expect(await classifyRoot(path, false)).toBe(archive ? "zip" : "file");
const target = await parseBinaryTarget(path);
if (archive)

View File

@ -11,43 +11,31 @@ afterEach(() => {
});
describe("CLI JSON input logging status", () => {
it.each([
{},
{ input_path: "input.json", input_reason: "invalid-json" },
{ input_path: "missing.json", input_reason: "read-failed" },
{ input_path: "", input_reason: "read-failed" },
])(
"preserves failure diagnostics and logs an error for %j",
async (metadata) => {
const parsed = await parseCliJsonInput("{", "test-input");
if (parsed.ok) throw new Error("Expected malformed inline JSON to fail");
const value = Object.assign({}, parsed.error, metadata);
const serialized = JSON.stringify(value);
const lines: string[] = [];
const logger = pino(
{ level: "info" },
{ write: (line) => lines.push(line) },
);
process.exitCode = undefined;
it("preserves parsed failure diagnostics and logs an error", async () => {
const parsed = await parseCliJsonInput("{", "test-input");
if (parsed.ok) throw new Error("Expected malformed inline JSON to fail");
const value = parsed.error;
const serialized = JSON.stringify(value);
const lines: string[] = [];
const logger = pino(
{ level: "info" },
{ write: (line) => lines.push(line) },
);
process.exitCode = undefined;
const output = await logCliCommand(
logger,
"test-input",
async () => value,
);
const output = await logCliCommand(logger, "test-input", async () => value);
expect(output).toBe(value);
expect(JSON.stringify(output)).toBe(serialized);
expect(process.exitCode).toBe(1);
expect(lines).toHaveLength(1);
expect(JSON.parse(lines.join(""))).toMatchObject({
level: 50,
command: "test-input",
status: "error",
msg: "CLI command failed",
});
},
);
expect(output).toBe(value);
expect(JSON.stringify(output)).toBe(serialized);
expect(process.exitCode).toBe(1);
expect(lines).toHaveLength(1);
expect(JSON.parse(lines.join(""))).toMatchObject({
level: 50,
command: "test-input",
status: "error",
msg: "CLI command failed",
});
});
it("leaves ordinary parsed JSON successful and unchanged", async () => {
const parsed = await parseCliJsonInput(