2026-07-13 19:09:27 +00:00
import { execFile } from "node:child_process" ;
2026-07-22 20:38:26 +08:00
import { mkdir , writeFile } from "node:fs/promises" ;
2026-07-14 06:28:10 +08:00
import { join } from "node:path" ;
2026-07-13 19:09:27 +00:00
import { promisify } from "node:util" ;
2026-07-14 06:28:10 +08:00
import { createPackageWithOptions } from "@electron/asar" ;
2026-07-13 19:09:27 +00:00
import { describe , expect , it } from "vitest" ;
2026-07-22 20:38:26 +08:00
import { createTestTempDirectory } from "./fixtures/temporaryDirectory.js" ;
import {
renderCliOutputArgumentError ,
sanitizeCliOutput ,
validateCliOutputArguments ,
} from "../src/cliOutput.js" ;
2026-07-13 19:09:27 +00:00
const execFileAsync = promisify ( execFile );
2026-07-14 05:03:08 +00:00
const CLI_INTEGRATION_TIMEOUT_MS = 60 _000 ;
2026-07-20 08:39:41 +00:00
const CLI_VARIANT_TIMEOUT_MS = 120 _000 ;
2026-07-13 19:09:27 +00:00
describe ( "CLI output boundary" , () => {
2026-07-22 20:38:26 +08:00
it ( "rejects token windows that would corrupt structured output" , () => {
for ( const format of [ "json" , "jsonl" , "yaml" ] as const ) {
const validation = validateCliOutputArguments ([
"providers" ,
"--token-limit" ,
"5" ,
"--format" ,
format ,
]);
expect ( validation ). toMatchObject ({
ok : false ,
format ,
code : "UNSUPPORTED_OUTPUT_COMBINATION" ,
});
if ( ! validation . ok ) {
const rendered = renderCliOutputArgumentError ( validation );
expect ( rendered ). not . toContain ( "[truncated:" );
if ( format === "json" || format === "jsonl" )
expect ( JSON . parse ( rendered )). toMatchObject ({
ok : false ,
error : { code : "UNSUPPORTED_OUTPUT_COMBINATION" },
});
else
expect ( rendered ). toMatch (
/^ok: false\nerror:\n code: UNSUPPORTED_OUTPUT_COMBINATION\n/u,
);
}
}
expect (
validateCliOutputArguments ([
"providers" ,
"--token-limit" ,
"5" ,
"--format" ,
"toon" ,
]),
). toEqual ({ ok : true });
expect (
validateCliOutputArguments ([ "providers" , "--token-count" , "--json" ]),
). toEqual ({ ok : true });
});
it ( "fails before emitting a truncated JSON document" , async () => {
const execution = execFileAsync (
process . execPath ,
[ "scripts/rea.mjs" , "providers" , "--token-limit" , "5" , "--json" ],
{ cwd : process.cwd () },
);
const failure = await execution . catch (( cause : unknown ) => cause );
expect ( failure ). toMatchObject ({ code : 1 });
const { stdout } = failure as { readonly stdout : string };
expect ( JSON . parse ( stdout )). toMatchObject ({
ok : false ,
error : { code : "UNSUPPORTED_OUTPUT_COMBINATION" },
});
});
2026-07-13 19:09:27 +00:00
it ( "preserves normal output and sanitizes text and JSON validation errors" , () => {
expect ( sanitizeCliOutput ( "result: ok\n" )). toBe ( "result: ok\n" );
expect ( sanitizeCliOutput ( "result: VALIDATION_ERROR\n" )). toBe (
"result: VALIDATION_ERROR\n" ,
);
const raw =
'code: VALIDATION_ERROR\nmessage: "raw Zod details"\nfieldErrors: SECRET\n' ;
expect ( sanitizeCliOutput ( raw )). toBe (
'code: VALIDATION_ERROR\nmessage: "REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again."\n' ,
);
expect (
JSON . parse (
sanitizeCliOutput (
JSON . stringify ({
code : "VALIDATION_ERROR" ,
message : "raw Zod details" ,
fieldErrors : [{ code : "invalid_type" }],
}),
),
),
). toEqual ({
code : "VALIDATION_ERROR" ,
message :
"REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again." ,
});
expect (
JSON . parse (
sanitizeCliOutput (
JSON . stringify ({
ok : false ,
error : {
code : "VALIDATION_ERROR" ,
message : "raw Zod details" ,
fieldErrors : [{ code : "invalid_type" }],
},
meta : { command : "analyze" },
}),
),
),
). toEqual ({
ok : false ,
error : {
code : "VALIDATION_ERROR" ,
message :
"REA could not read the command arguments. Run `rea --help`, correct the arguments, then try again." ,
},
meta : { command : "analyze" },
});
});
2026-07-14 05:03:08 +00:00
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' ,
});
},
CLI_INTEGRATION_TIMEOUT_MS ,
);
2026-07-14 06:28:10 +08:00
2026-07-14 05:03:08 +00:00
it (
"preserves artifact diagnostics in ordinary and full JSON output" ,
async () => {
2026-07-22 20:38:26 +08:00
const root = await createTestTempDirectory ( "rea-cli-diagnostics-" );
2026-07-14 05:03:08 +00:00
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" );
2026-07-14 06:28:10 +08:00
2026-07-14 05:03:08 +00:00
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 ,
);
2026-07-14 06:33:34 +08:00
2026-07-14 05:03:08 +00:00
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" ],
];
2026-07-14 06:33:34 +08:00
2026-07-14 05:03:08 +00:00
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 ) });
}
},
2026-07-20 08:39:41 +00:00
CLI_VARIANT_TIMEOUT_MS ,
2026-07-14 05:03:08 +00:00
);
it (
"keeps failure logs out of structured stdout" ,
async () => {
const execution = execFileAsync (
process . execPath ,
[
2026-07-14 06:33:34 +08:00
"scripts/rea.mjs" ,
2026-07-14 05:03:08 +00:00
"--json" ,
2026-07-14 06:33:34 +08:00
"investigate-versions" ,
"/tmp/left" ,
"/tmp/right" ,
"/tmp/workspace.json" ,
2026-07-14 05:03:08 +00:00
],
{ 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 ,
);
2026-07-13 19:09:27 +00:00
});