import { describe, expect, it } from "vitest"; import { ArtifactOperationError } from "./domain/artifactOperationError.js"; import { ConfigurationError } from "./domain/configurationErrors.js"; import { projectAnalysisError } from "./domain/analysisErrorProjection.js"; import { isCliOperationFailure, logCliCommand } from "./cliLogging.js"; import { silentLogger } from "./logger.js"; import { runSetup } from "./application/Setup.js"; import { isSetupFailure, type SetupResult } from "./application/SetupTypes.js"; import { FakeSetupHost, options as setupOptions, } from "./application/Setup.fixture.js"; import { err } from "./domain/result.js"; import { isUpdateFailure, runUpdate, type UpdateHost, } from "./application/Update.js"; import { isUninstallFailure, runUninstall, type UninstallHost, } from "./application/Uninstall.js"; import { CLI_COMMANDS } from "./cliCommandNames.js"; it("does not treat ordinary error-looking data or nested Evidence as command failure", () => { expect( isCliOperationFailure({ code: "configuration_invalid", message: "source data", }), ).toBe(false); expect( isCliOperationFailure({ result: projectAnalysisError(new ConfigurationError("source setting")), }), ).toBe(false); }); describe("CLI command outcome classification", () => { it("returns nonzero for the UpdateResult failure variant", async () => { const installation = { manager: "npm", prefix: "/fixture", packageRoot: "/fixture/lib/node_modules/rea-agents", } as const; const updateHost: UpdateHost = { installation: () => Promise.resolve(installation), latestVersion: () => Promise.resolve( err({ kind: "unavailable", detail: "registry unavailable" }), ), installVersion: () => Promise.resolve(err("not called")), installedVersion: () => Promise.resolve(err("not called")), planMaintenance: () => Promise.resolve({ status: "current", plannedActions: [] }), }; const result = await runUpdate("3.2.1", updateHost); expect(result.status).toBe("failed"); const previousExitCode = process.exitCode; const output = await logCliCommand( silentLogger, CLI_COMMANDS.update, async () => result, isUpdateFailure, ); expect(output).toBe(result); expect(process.exitCode).toBe(1); process.exitCode = previousExitCode; }); it.each(["needs_confirmation", "needs_human"] as const)( "returns nonzero for a real setup result with status %s", async (expectedStatus) => { const host = new FakeSetupHost(); const setup: SetupResult = expectedStatus === "needs_human" ? await runSetup( { ...setupOptions(false), structured: false }, host, () => Promise.resolve(true), ) : await runSetup( { ...setupOptions(false), clientIds: ["cursor"] }, Object.assign(host, { clients: [{ name: "cursor", configPath: "/cursor.json" }], }), ); expect(setup.status).toBe(expectedStatus); const previousExitCode = process.exitCode; const output = await logCliCommand( silentLogger, CLI_COMMANDS.setup, async () => setup, isSetupFailure, ); expect(output).toBe(setup); expect(process.exitCode).toBe(1); process.exitCode = previousExitCode; }, ); it("returns nonzero when the real uninstall outcome contains a failed action", async () => { const host: UninstallHost = { clients: () => Promise.resolve([]), inspectClient: () => Promise.resolve(undefined), removeClient: () => Promise.resolve({ name: "client", status: "failed", detail: "write" }), removeSkill: () => Promise.resolve({ name: "skill", status: "failed", detail: "write" }), purgeData: () => Promise.resolve([]), }; const result = await runUninstall(false, host); expect(result.status).toBe("failed"); const previousExitCode = process.exitCode; await logCliCommand( silentLogger, CLI_COMMANDS.uninstall, async () => result, isUninstallFailure, ); expect(process.exitCode).toBe(1); process.exitCode = previousExitCode; }); }); describe("CLI thrown analysis failures", () => { it("returns the shared projection with actionable failure coordinates", async () => { const previousExitCode = process.exitCode; try { const projected = await logCliCommand( silentLogger, "fixture", async () => { throw new ArtifactOperationError( "analyze_javascript_application", "integrity", { logicalPath: "src/main.js", declaredSha256: "a".repeat(64), calculatedSha256: "b".repeat(64), unpacked: true, }, ); }, ); expect(projected).toMatchObject({ code: "artifact_integrity_mismatch", category: "integrity_mismatch", details: { logical_path: "src/main.js", declared_sha256: "a".repeat(64), calculated_sha256: "b".repeat(64), }, }); } finally { process.exitCode = previousExitCode; } }); });