diff --git a/tests/cliOutput.test.ts b/tests/cliOutput.test.ts index a67b9008e..5373a070d 100644 --- a/tests/cliOutput.test.ts +++ b/tests/cliOutput.test.ts @@ -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, + ); }); diff --git a/tests/processCli.test.ts b/tests/processCli.test.ts index eef94c0de..3c8372190 100644 --- a/tests/processCli.test.ts +++ b/tests/processCli.test.ts @@ -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 => { }; 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(