test(cli): reduce repeated startup and filesystem setup
This commit is contained in:
parent
9c3479ed95
commit
d88d6c1897
|
|
@ -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,
|
||||
}) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"]],
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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-");
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue