mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
environment-build/export ran an unclassified recursive mkdir of the destination's parent before SafeOutputTree validated anything. A trailing `..` therefore created directories before it was refused, and a regular file in the parent path surfaced as unreadable_output instead of refusing the selection. SafeOutputTree.create gains a createParents option that admits the root's own syntax first, then creates the parents, mapping EEXIST and ENOTDIR to a destination refusal. The environment bundle writer uses it instead of its own mkdir. Fixes #7688 Fixes #7571 The non-directory mapping comes from #7734 by HeadmasterEggy, and the syntax-first check from #7588 by harshitethic; their tests are kept. Co-Authored-By: Avocado <headmastereggy@gmail.com> Co-Authored-By: Harshit Sharma <66710144+harshitethic@users.noreply.github.com> Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
100 lines
3.7 KiB
TypeScript
100 lines
3.7 KiB
TypeScript
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
import { writeEnvironmentServeFiles } from "../../fixtures/environmentServe.js";
|
|
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest.for(["environment-export", "environment-build"] as const)(
|
|
"%s attributes non-directory parents to the selected destination",
|
|
async (command, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-bundle-parent-");
|
|
const files = await writeEnvironmentServeFiles(root);
|
|
const occupied = join(root, "parentfile");
|
|
await writeFile(occupied, "keep\n");
|
|
const before = await stat(occupied);
|
|
const entries = await readdir(root);
|
|
const call = await cli.sharedMcpCall?.();
|
|
if (call === undefined) throw new Error("Expected stdio MCP session");
|
|
for (const directory of [
|
|
join(occupied, "bundle"),
|
|
join(occupied, "missing", "bundle"),
|
|
]) {
|
|
const arguments_ =
|
|
command === "environment-export"
|
|
? [command, files.manifestPath, "--evidence", files.capturePath]
|
|
: [command, files.capturePath];
|
|
const result = await cli.run({
|
|
arguments: [...arguments_, directory, "--json"],
|
|
});
|
|
expect(result.exitCode, result.stderr).toBe(1);
|
|
expect(result.json).toMatchObject({
|
|
code: "invalid_request",
|
|
category: "invalid_input",
|
|
details: {
|
|
issues: [{ path: ["directory"], reason: "invalid_value" }],
|
|
},
|
|
recovery: {
|
|
kind: "inspect_input_contract",
|
|
operation: "environment-export",
|
|
},
|
|
});
|
|
const mcp = await call("export_environment_bundle", {
|
|
source: { kind: "evidence-file", path: files.capturePath },
|
|
output_directory: directory,
|
|
});
|
|
expect(mcp.isError).toBe(true);
|
|
expect(parseMcpToolError(mcp).error).toMatchObject({
|
|
code: "invalid_request",
|
|
category: "invalid_input",
|
|
details: {
|
|
issues: [{ path: ["output_directory"], reason: "invalid_value" }],
|
|
},
|
|
recovery: {
|
|
kind: "inspect_input_contract",
|
|
operation: "export_environment_bundle",
|
|
},
|
|
});
|
|
expect(await readFile(occupied, "utf8")).toBe("keep\n");
|
|
const after = await stat(occupied);
|
|
expect({ dev: after.dev, ino: after.ino }).toEqual({
|
|
dev: before.dev,
|
|
ino: before.ino,
|
|
});
|
|
expect(await readdir(root)).toEqual(entries);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(["environment-export", "environment-build"] as const)(
|
|
"%s still creates missing parents and accepts an empty destination",
|
|
async (command, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-bundle-parent-control-");
|
|
const files = await writeEnvironmentServeFiles(root);
|
|
const empty = join(root, "empty");
|
|
await mkdir(empty);
|
|
for (const directory of [
|
|
join(root, "missing", "nested", "bundle"),
|
|
empty,
|
|
]) {
|
|
const arguments_ =
|
|
command === "environment-export"
|
|
? [command, files.manifestPath, "--evidence", files.capturePath]
|
|
: [command, files.capturePath];
|
|
const result = await cli.run({
|
|
arguments: [...arguments_, directory, "--json"],
|
|
});
|
|
expect(result.exitCode, result.stderr).toBe(0);
|
|
expect(result.json).toMatchObject({
|
|
status: command === "environment-export" ? "exported" : "built",
|
|
});
|
|
expect(
|
|
(
|
|
await readFile(join(directory, "envs/grafana-example/evidence.json"))
|
|
).equals(await readFile(files.capturePath)),
|
|
).toBe(true);
|
|
}
|
|
},
|
|
);
|