Files
rea/tests/boundary/cli/environmentExportInputsCli.test.ts
T
morluto c324d31b45 test(environment): verify export failures through the real CLI
Remove shared-module mocks for input reads, heap statistics, and tarball streaming. Exercise actual file changes and real V8 heap admission in fresh CLI processes; keep only selected-descriptor syscall fault injection for the pathless EIO case.

Reproduced the four hosted source failures with a preloaded module graph and found the same flaw in the adjacent tarball test. The preloaded control now passes 17 cases; 32 focused tests, all 1,739 service tests (2 skips), and check:ci pass. Snapshot, cleanup, identity, and error projection coverage is retained.
2026-10-21 18:26:58 +08:00

194 lines
6.7 KiB
TypeScript

import {
appendFile,
readFile,
stat,
truncate,
writeFile,
} from "node:fs/promises";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { expect } from "vitest";
import { z } from "zod";
import { nativeJsonParseRequiredBytes } from "../../../src/application/JsonInputFile.js";
import { writeEnvironmentServeFiles } from "../../fixtures/environmentServe.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
const probe = pathToFileURL(
resolve("tests/fixtures/environmentExportInputProbe.mjs"),
).href;
for (const role of ["manifest", "evidence"] as const) {
cliTest(
`refuses a real ${role} file changed during its read`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-export-input-race-cli-");
const files = await writeEnvironmentServeFiles(root);
const input =
role === "manifest" ? files.manifestPath : files.capturePath;
const before = await readFile(input);
const marker = join(root, "read-marker");
const directory = join(root, "bundle");
const result = await cli.run({
arguments: [
"environment-export",
files.manifestPath,
"--evidence",
files.capturePath,
directory,
"--json",
],
environment: {
NODE_OPTIONS: `--import=${probe} --max-old-space-size=256`,
REA_TEST_EXPORT_INPUT: input,
REA_TEST_EXPORT_READ_MARKER: marker,
REA_TEST_EXPORT_READ_ACTION: "append",
},
timeoutMs: 30_000,
});
expect(await readFile(marker, "utf8")).toBe("read");
expect(await readFile(input)).toEqual(
Buffer.concat([before, Buffer.from(" ")]),
);
expect(result.exitCode, result.stderr).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: {
operation: "environment-export",
issues: [{ path: [role], reason: "invalid_value" }],
},
});
await expect(stat(directory)).rejects.toMatchObject({ code: "ENOENT" });
},
);
}
const heapRefusalSchema = z.object({
code: z.literal("resource_constraint"),
details: z.object({
operation: z.literal("environment-export"),
resource: z.literal("memory"),
reported_limits: z.object({
input_path: z.string(),
input_file_bytes: z.number(),
required_heap_bytes: z.number(),
available_heap_bytes: z.number(),
}),
}),
});
cliTest(
"admits neither the whole read nor an unbudgeted snapshot under a real heap limit",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-export-input-heap-cli-");
const files = await writeEnvironmentServeFiles(root);
const originalBytes = (await stat(files.manifestPath)).size;
const marker = join(root, "read-marker");
const directory = join(root, "bundle");
// Whitespace preserves a valid manifest while making the actual file large.
await appendFile(files.manifestPath, Buffer.alloc(8 * 1024 * 1024, " "));
let available: number | undefined;
for (const phase of ["whole-read", "snapshot"] as const) {
if (available !== undefined) {
// Fit the native parse in two thirds of the previous child's real
// headroom, while the additional snapshot reservation cannot fit.
const overhead = nativeJsonParseRequiredBytes(0);
const perByte = nativeJsonParseRequiredBytes(1) - overhead;
await truncate(
files.manifestPath,
Math.max(
originalBytes,
Math.floor(((available * 2) / 3 - overhead) / perByte),
),
);
}
const result = await cli.run({
arguments: [
"environment-export",
files.manifestPath,
"--evidence",
files.capturePath,
directory,
"--json",
],
environment: {
NODE_OPTIONS: `--import=${probe} --max-old-space-size=256`,
REA_TEST_EXPORT_INPUT: files.manifestPath,
REA_TEST_EXPORT_READ_MARKER: marker,
REA_TEST_EXPORT_READ_ACTION: "record",
},
timeoutMs: 30_000,
});
expect(result.exitCode, result.stderr).toBe(1);
const limits = heapRefusalSchema.parse(result.json).details
.reported_limits;
expect(limits.input_path).toBe(files.manifestPath);
expect(limits.input_file_bytes).toBe(
(await stat(files.manifestPath)).size,
);
expect(limits.required_heap_bytes).toBe(
nativeJsonParseRequiredBytes(limits.input_file_bytes, true),
);
expect(limits.required_heap_bytes).toBeGreaterThan(
limits.available_heap_bytes,
);
if (phase === "snapshot")
expect(
nativeJsonParseRequiredBytes(limits.input_file_bytes),
).toBeLessThan(limits.available_heap_bytes);
else
expect(
nativeJsonParseRequiredBytes(limits.input_file_bytes),
).toBeGreaterThan(limits.available_heap_bytes);
available = limits.available_heap_bytes;
await expect(stat(marker)).rejects.toMatchObject({ code: "ENOENT" });
await expect(stat(directory)).rejects.toMatchObject({ code: "ENOENT" });
}
},
);
cliTest(
"attributes a pathless descriptor failure during the real tarball copy",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-export-copy-read-cli-");
const files = await writeEnvironmentServeFiles(root);
const tarball = join(root, "package.tgz");
await writeFile(tarball, "admitted package");
const marker = join(root, "read-marker");
const directory = join(root, "bundle");
const result = await cli.run({
arguments: [
"environment-export",
files.manifestPath,
"--evidence",
files.capturePath,
directory,
"--package-tarball",
tarball,
"--json",
],
environment: {
NODE_OPTIONS: `--import=${probe} --max-old-space-size=256`,
REA_TEST_EXPORT_INPUT: tarball,
REA_TEST_EXPORT_READ_MARKER: marker,
REA_TEST_EXPORT_READ_ACTION: "fail-copy",
},
timeoutMs: 30_000,
});
expect(await readFile(marker, "utf8")).toBe("copy");
expect(await readFile(tarball, "utf8")).toBe("admitted package");
expect(result.exitCode, result.stderr).toBe(1);
expect(result.json).toMatchObject({
code: "unreadable_output",
details: {
reason: expect.stringContaining(`reading ${tarball}`),
diagnostics: { output_directory: directory },
},
remediation: { action: expect.stringContaining(tarball) },
});
expect(JSON.stringify(result.json)).not.toContain("disk space");
},
);