test(cli): allow cold-start integration timing
This commit is contained in:
parent
5279d7755c
commit
7d5621dfbc
|
|
@ -10,6 +10,7 @@ import { describe, expect, it } from "vitest";
|
|||
import { sanitizeCliOutput } from "../src/cliOutput.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const CLI_INTEGRATION_TIMEOUT_MS = 60_000;
|
||||
|
||||
describe("CLI output boundary", () => {
|
||||
it("preserves normal output and sanitizes text and JSON validation errors", () => {
|
||||
|
|
@ -62,109 +63,125 @@ describe("CLI output boundary", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("sanitizes a real missing-argument dispatcher failure", async () => {
|
||||
await expect(
|
||||
execFileAsync(process.execPath, ["scripts/rea.mjs", "analyze"], {
|
||||
cwd: process.cwd(),
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
stdout:
|
||||
'code: VALIDATION_ERROR\nmessage: "REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again."\n',
|
||||
});
|
||||
await expect(
|
||||
execFileAsync(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "--full-output", "--json", "analyze"],
|
||||
{ cwd: process.cwd() },
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
stdout: expect.not.stringContaining("fieldErrors"),
|
||||
});
|
||||
await expect(
|
||||
execFileAsync(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "--full-output", "analyze"],
|
||||
{ cwd: process.cwd() },
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
stdout:
|
||||
'ok: false\nerror:\n code: VALIDATION_ERROR\n message: "REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again."\n',
|
||||
});
|
||||
}, 20_000);
|
||||
|
||||
it("preserves artifact diagnostics in ordinary and full JSON output", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-cli-diagnostics-"));
|
||||
const source = join(root, "source");
|
||||
await mkdir(source);
|
||||
await writeFile(join(source, "main.js"), "console.log('ok');\n");
|
||||
const archive = join(root, "fixture.asar");
|
||||
await createPackageWithOptions(source, archive, { unpack: "*.js" });
|
||||
await writeFile(join(`${archive}.unpacked`, "main.js"), "changed();\n");
|
||||
|
||||
for (const flags of [["--json"], ["--full-output", "--json"]]) {
|
||||
const execution = execFileAsync(process.execPath, [
|
||||
"scripts/rea.mjs",
|
||||
...flags,
|
||||
"inventory-artifact",
|
||||
archive,
|
||||
]);
|
||||
const failure = await execution.catch((cause: unknown) => cause);
|
||||
expect(failure).toMatchObject({ code: 1 });
|
||||
const { stdout } = failure as { readonly stdout: string };
|
||||
const output = JSON.stringify(JSON.parse(stdout) as unknown);
|
||||
expect(output).toContain('"logical_path":"main.js"');
|
||||
expect(output).toMatch(/"declared_sha256":"[a-f0-9]{64}"/u);
|
||||
expect(output).toMatch(/"calculated_sha256":"[a-f0-9]{64}"/u);
|
||||
expect(output).toContain('"unpacked":true');
|
||||
}
|
||||
}, 20_000);
|
||||
|
||||
it("keeps operation failure status independent of output controls", async () => {
|
||||
const variants = [
|
||||
["--format", "toon"],
|
||||
["--format", "json"],
|
||||
["--format", "yaml"],
|
||||
["--format", "md"],
|
||||
["--format", "jsonl"],
|
||||
["--full-output", "--json"],
|
||||
["--filter-output", "category", "--json"],
|
||||
["--token-limit", "5", "--json"],
|
||||
["--token-count", "--json"],
|
||||
];
|
||||
|
||||
for (const flags of variants) {
|
||||
it(
|
||||
"sanitizes a real missing-argument dispatcher failure",
|
||||
async () => {
|
||||
await expect(
|
||||
execFileAsync(process.execPath, [
|
||||
execFileAsync(process.execPath, ["scripts/rea.mjs", "analyze"], {
|
||||
cwd: process.cwd(),
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
stdout:
|
||||
'code: VALIDATION_ERROR\nmessage: "REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again."\n',
|
||||
});
|
||||
await expect(
|
||||
execFileAsync(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "--full-output", "--json", "analyze"],
|
||||
{ cwd: process.cwd() },
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
stdout: expect.not.stringContaining("fieldErrors"),
|
||||
});
|
||||
await expect(
|
||||
execFileAsync(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "--full-output", "analyze"],
|
||||
{ cwd: process.cwd() },
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
stdout:
|
||||
'ok: false\nerror:\n code: VALIDATION_ERROR\n message: "REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again."\n',
|
||||
});
|
||||
},
|
||||
CLI_INTEGRATION_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
it(
|
||||
"preserves artifact diagnostics in ordinary and full JSON output",
|
||||
async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-cli-diagnostics-"));
|
||||
const source = join(root, "source");
|
||||
await mkdir(source);
|
||||
await writeFile(join(source, "main.js"), "console.log('ok');\n");
|
||||
const archive = join(root, "fixture.asar");
|
||||
await createPackageWithOptions(source, archive, { unpack: "*.js" });
|
||||
await writeFile(join(`${archive}.unpacked`, "main.js"), "changed();\n");
|
||||
|
||||
for (const flags of [["--json"], ["--full-output", "--json"]]) {
|
||||
const execution = execFileAsync(process.execPath, [
|
||||
"scripts/rea.mjs",
|
||||
...flags,
|
||||
"inventory-artifact",
|
||||
archive,
|
||||
]);
|
||||
const failure = await execution.catch((cause: unknown) => cause);
|
||||
expect(failure).toMatchObject({ code: 1 });
|
||||
const { stdout } = failure as { readonly stdout: string };
|
||||
const output = JSON.stringify(JSON.parse(stdout) as unknown);
|
||||
expect(output).toContain('"logical_path":"main.js"');
|
||||
expect(output).toMatch(/"declared_sha256":"[a-f0-9]{64}"/u);
|
||||
expect(output).toMatch(/"calculated_sha256":"[a-f0-9]{64}"/u);
|
||||
expect(output).toContain('"unpacked":true');
|
||||
}
|
||||
},
|
||||
CLI_INTEGRATION_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
it(
|
||||
"keeps operation failure status independent of output controls",
|
||||
async () => {
|
||||
const variants = [
|
||||
["--format", "toon"],
|
||||
["--format", "json"],
|
||||
["--format", "yaml"],
|
||||
["--format", "md"],
|
||||
["--format", "jsonl"],
|
||||
["--full-output", "--json"],
|
||||
["--filter-output", "category", "--json"],
|
||||
["--token-limit", "5", "--json"],
|
||||
["--token-count", "--json"],
|
||||
];
|
||||
|
||||
for (const flags of variants) {
|
||||
await expect(
|
||||
execFileAsync(process.execPath, [
|
||||
"scripts/rea.mjs",
|
||||
...flags,
|
||||
"investigate-versions",
|
||||
"/tmp/left",
|
||||
"/tmp/right",
|
||||
"/tmp/workspace.json",
|
||||
]),
|
||||
).rejects.toMatchObject({ code: 1, stdout: expect.any(String) });
|
||||
}
|
||||
},
|
||||
CLI_INTEGRATION_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
it(
|
||||
"keeps failure logs out of structured stdout",
|
||||
async () => {
|
||||
const execution = execFileAsync(
|
||||
process.execPath,
|
||||
[
|
||||
"scripts/rea.mjs",
|
||||
"--json",
|
||||
"investigate-versions",
|
||||
"/tmp/left",
|
||||
"/tmp/right",
|
||||
"/tmp/workspace.json",
|
||||
]),
|
||||
).rejects.toMatchObject({ code: 1, stdout: expect.any(String) });
|
||||
}
|
||||
}, 20_000);
|
||||
|
||||
it("keeps failure logs out of structured stdout", async () => {
|
||||
const execution = execFileAsync(
|
||||
process.execPath,
|
||||
[
|
||||
"scripts/rea.mjs",
|
||||
"--json",
|
||||
"investigate-versions",
|
||||
"/tmp/left",
|
||||
"/tmp/right",
|
||||
"/tmp/workspace.json",
|
||||
],
|
||||
{ env: { ...process.env, REA_LOG_LEVEL: "error" } },
|
||||
);
|
||||
const failure = await execution.catch((cause: unknown) => cause);
|
||||
expect(failure).toMatchObject({
|
||||
code: 1,
|
||||
stderr: expect.stringContaining('"status":"error"'),
|
||||
});
|
||||
const { stdout } = failure as { readonly stdout: string };
|
||||
expect(JSON.parse(stdout)).toMatchObject({ error: "ApprovalRequired" });
|
||||
}, 20_000);
|
||||
],
|
||||
{ env: { ...process.env, REA_LOG_LEVEL: "error" } },
|
||||
);
|
||||
const failure = await execution.catch((cause: unknown) => cause);
|
||||
expect(failure).toMatchObject({
|
||||
code: 1,
|
||||
stderr: expect.stringContaining('"status":"error"'),
|
||||
});
|
||||
const { stdout } = failure as { readonly stdout: string };
|
||||
expect(JSON.parse(stdout)).toMatchObject({ error: "ApprovalRequired" });
|
||||
},
|
||||
CLI_INTEGRATION_TIMEOUT_MS,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { PROCESS_PROVIDER } from "../src/application/ProcessEvidence.js";
|
|||
|
||||
const roots: string[] = [];
|
||||
const execFileAsync = promisify(execFile);
|
||||
const CLI_INTEGRATION_TIMEOUT_MS = 60_000;
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
|
|
@ -31,18 +32,22 @@ const fixture = async (): Promise<string> => {
|
|||
};
|
||||
|
||||
describe("process CLI errors", () => {
|
||||
it("exits unsuccessfully without writing failure-shaped evidence", async () => {
|
||||
await expect(
|
||||
execFileAsync(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "capture-process", "/missing/scenario.json"],
|
||||
{ cwd: process.cwd() },
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
code: 1,
|
||||
stdout: expect.stringContaining("category: invalid_input"),
|
||||
});
|
||||
});
|
||||
it(
|
||||
"exits unsuccessfully without writing failure-shaped evidence",
|
||||
async () => {
|
||||
await expect(
|
||||
execFileAsync(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", "capture-process", "/missing/scenario.json"],
|
||||
{ cwd: process.cwd() },
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
code: 1,
|
||||
stdout: expect.stringContaining("category: invalid_input"),
|
||||
});
|
||||
},
|
||||
CLI_INTEGRATION_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
it("projects unexpected causes without their message or stack", () => {
|
||||
const projected = projectProcessCliError(
|
||||
|
|
|
|||
Loading…
Reference in New Issue