From 2fd11961cfba0c2d211a1aa643cbf05fa4e8c20d Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:59:05 -0400 Subject: [PATCH] fix(cli): fail commands returning projected analysis errors (#506) Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> --- src/cliLogging.test.ts | 16 ++++++++ src/cliLogging.ts | 2 + .../boundary/cli/projectedErrorStatus.test.ts | 41 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 tests/boundary/cli/projectedErrorStatus.test.ts diff --git a/src/cliLogging.test.ts b/src/cliLogging.test.ts index f941e3388..28eef61fd 100644 --- a/src/cliLogging.test.ts +++ b/src/cliLogging.test.ts @@ -1,9 +1,15 @@ import { describe, expect, it } from "vitest"; +import { ConfigurationError, projectAnalysisError } from "./domain/errors.js"; + import { isCliOperationFailure } from "./cliLogging.js"; describe("CLI operation status classification", () => { it.each([ + [ + "projected error", + projectAnalysisError(new ConfigurationError("invalid setting")), + ], ["typed error", { error: "Analysis failed", category: "timeout" }], ["unhealthy diagnostic", { healthy: false, checks: [] }], ["failed status", { status: "failed" }], @@ -15,6 +21,16 @@ describe("CLI operation status classification", () => { }); it.each([ + [ + "ordinary data with code", + { code: "configuration_invalid", message: "source data" }, + ], + [ + "evidence about an error", + { + result: projectAnalysisError(new ConfigurationError("source setting")), + }, + ], ["ready setup", { status: "ready" }], ["complete uninstall", { status: "complete" }], ["current version", { status: "current" }], diff --git a/src/cliLogging.ts b/src/cliLogging.ts index cd5040552..c9476a197 100644 --- a/src/cliLogging.ts +++ b/src/cliLogging.ts @@ -1,3 +1,4 @@ +import { analysisErrorProjectionSchema } from "./contracts/errorSchemas.js"; import type { Logger } from "./logger.js"; const isRecord = (value: unknown): value is Readonly> => @@ -7,6 +8,7 @@ const isRecord = (value: unknown): value is Readonly> => export const isCliOperationFailure = (value: unknown): boolean => { if (!isRecord(value)) return false; if (typeof value.error === "string") return true; + if (analysisErrorProjectionSchema.safeParse(value).success) return true; if (value.healthy === false) return true; return ( value.status === "failed" || diff --git a/tests/boundary/cli/projectedErrorStatus.test.ts b/tests/boundary/cli/projectedErrorStatus.test.ts new file mode 100644 index 000000000..a6e83edf2 --- /dev/null +++ b/tests/boundary/cli/projectedErrorStatus.test.ts @@ -0,0 +1,41 @@ +import { describe, expect } from "vitest"; + +import { cliTest } from "../../support/cli/cliFixture.js"; + +describe("projected CLI failures", () => { + cliTest( + "exits unsuccessfully for invalid replay configuration", + async ({ cli }) => { + const result = await cli.run({ + arguments: ["run-controlled-replay", "{}", "--json"], + environment: { REA_JAVASCRIPT_REPLAY_ENABLED: "not-a-bool" }, + }); + expect(result.json).toMatchObject({ + code: "configuration_invalid", + category: "execution_failure", + retryable: false, + }); + expect(result.exitCode).toBe(1); + }, + ); + + cliTest( + "exits unsuccessfully for invalid managed reconstruction input", + async ({ cli }) => { + const result = await cli.run({ + arguments: ["import-managed-reconstruction", "{}", "--json"], + }); + expect(result.json).toMatchObject({ code: "invalid_request" }); + expect(result.exitCode).toBe(1); + }, + ); + + cliTest( + "keeps successful target-free inventory commands successful", + async ({ cli }) => { + const result = await cli.run({ arguments: ["capabilities", "--json"] }); + expect(result.exitCode).toBe(0); + expect(result.json).toBeDefined(); + }, + ); +});