mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
252 lines
8.1 KiB
TypeScript
252 lines
8.1 KiB
TypeScript
import { Writable } from "node:stream";
|
|
|
|
import { Cli } from "incur";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { parseCliOutputArguments } from "../../../src/cliOutput.js";
|
|
import {
|
|
createCliResultOutput,
|
|
streamCliCommandResults,
|
|
} from "../../../src/cli/cliResultOutput.js";
|
|
import {
|
|
cliOperationFailureIdentity,
|
|
cliOperationFailurePayload,
|
|
logCliCommand,
|
|
} from "../../../src/cliLogging.js";
|
|
import { ArtifactOperationError } from "../../../src/domain/artifactOperationError.js";
|
|
import { silentLogger } from "../../../src/logger.js";
|
|
|
|
const metadata = { command: "fixture", duration: "1ms", format: "toon" };
|
|
|
|
describe("CLI formatting stage admission", () => {
|
|
it("preserves native CTA sentinel output", async () => {
|
|
const replies: string[] = [];
|
|
for (const wrapped of [false, true]) {
|
|
const cli = Cli.create("rea-output-test", { sync: false });
|
|
cli.command("fixture", {
|
|
run: (context) =>
|
|
context.ok(
|
|
{ value: 42 },
|
|
{ cta: { commands: [{ command: "next" }] } },
|
|
),
|
|
});
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments(["fixture"]),
|
|
new Writable(),
|
|
);
|
|
if (output === undefined) throw new Error("Missing output owner");
|
|
if (wrapped) streamCliCommandResults(cli, output);
|
|
let reply = "";
|
|
await cli.serve(["fixture"], {
|
|
stdout: (text) => {
|
|
reply += text;
|
|
},
|
|
});
|
|
expect(output.handled).toBe(false);
|
|
replies.push(reply);
|
|
}
|
|
expect(replies[1]).toBe(replies[0]);
|
|
expect(replies[0]).toContain("next");
|
|
});
|
|
|
|
it("captures thrown integrity failures without re-reading diagnostics during recovery", async () => {
|
|
const previousExitCode = process.exitCode;
|
|
try {
|
|
const value = await logCliCommand(silentLogger, "fixture", async () => {
|
|
throw new ArtifactOperationError("inspect_artifact", "integrity", {
|
|
logicalPath: "entry.js",
|
|
declaredSha256: "a".repeat(64),
|
|
calculatedSha256: "b".repeat(64),
|
|
unpacked: false,
|
|
});
|
|
});
|
|
// A second traversal would be observable here. The producer already
|
|
// classified this exact projection before the formatter sees it.
|
|
Object.defineProperty(value, "details", {
|
|
get() {
|
|
throw new Error("Diagnostics were interpreted again");
|
|
},
|
|
});
|
|
expect(cliOperationFailurePayload(value)).toBe(value);
|
|
expect(cliOperationFailureIdentity(value)).toEqual({
|
|
code: "artifact_integrity_mismatch",
|
|
category: "integrity_mismatch",
|
|
retryable: false,
|
|
});
|
|
} finally {
|
|
process.exitCode = previousExitCode;
|
|
}
|
|
});
|
|
|
|
it("declines errors before a result or from a different failure class", async () => {
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments([]),
|
|
new Writable(),
|
|
);
|
|
expect(output).toBeDefined();
|
|
expect(
|
|
await output?.recoverFormattingFailure(
|
|
new RangeError("Invalid string length"),
|
|
),
|
|
).toBe(false);
|
|
await output?.write({ result: "completed" }, metadata);
|
|
for (const cause of [
|
|
new Error("Invalid string length"),
|
|
new RangeError("Maximum call stack size exceeded"),
|
|
new Error("operation failed"),
|
|
])
|
|
expect(await output?.recoverFormattingFailure(cause)).toBe(false);
|
|
expect(output?.handled).toBe(false);
|
|
});
|
|
|
|
it("leaves command RangeErrors under Incur's command failure protocol", async () => {
|
|
const cli = Cli.create("rea-output-test", { sync: false });
|
|
cli.command("fixture", {
|
|
run: () => {
|
|
throw new RangeError("Invalid string length");
|
|
},
|
|
});
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments([]),
|
|
new Writable(),
|
|
);
|
|
if (output === undefined) throw new Error("Missing output owner");
|
|
streamCliCommandResults(cli, output);
|
|
let text = "";
|
|
let status = 0;
|
|
await cli.serve(["fixture"], {
|
|
stdout: (part) => {
|
|
text += part;
|
|
},
|
|
exit: (code) => {
|
|
status = code;
|
|
},
|
|
});
|
|
expect(status).toBe(1);
|
|
expect(text).toContain("UNKNOWN");
|
|
expect(text).not.toContain("resource_constraint");
|
|
expect(output.error).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("CLI formatting recovery outcomes", () => {
|
|
it("formats a recovered Markdown result through the CLI Markdown owner", async () => {
|
|
const cause = new RangeError("Invalid string length");
|
|
const chunks: Buffer[] = [];
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments(["--format", "md"]),
|
|
new Writable({
|
|
write(chunk: Buffer, _encoding, callback) {
|
|
chunks.push(chunk);
|
|
callback();
|
|
},
|
|
}),
|
|
);
|
|
const value = new Proxy(
|
|
{},
|
|
{
|
|
ownKeys: () => {
|
|
throw cause;
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(await output?.write(value, { ...metadata, format: "md" })).toBe(
|
|
true,
|
|
);
|
|
expect(output?.error).toMatchObject({ code: "resource_constraint" });
|
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
expect(text).toContain("resource_constraint");
|
|
expect(text).toContain("original result was not written");
|
|
expect(output?.failed).toBe(false);
|
|
});
|
|
|
|
it("retains ownership if the recovery document cannot be written", async () => {
|
|
const cause = new RangeError("Invalid string length");
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments([]),
|
|
new Writable({
|
|
write(_chunk, _encoding, callback) {
|
|
callback(cause);
|
|
},
|
|
}),
|
|
);
|
|
await output?.write({ result: "completed" }, metadata);
|
|
await expect(output?.recoverFormattingFailure(cause)).rejects.toBe(cause);
|
|
expect(output?.handled).toBe(true);
|
|
expect(output?.failed).toBe(true);
|
|
expect(await output?.recoverFormattingFailure(cause)).toBe(false);
|
|
});
|
|
|
|
it("does not replace a failed destination, even for the same RangeError", async () => {
|
|
const cause = new RangeError("Invalid string length");
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments(["--json"]),
|
|
new Writable({
|
|
write(_chunk, _encoding, callback) {
|
|
callback(cause);
|
|
},
|
|
}),
|
|
);
|
|
await expect(
|
|
output?.write({ result: "completed" }, { ...metadata, format: "json" }),
|
|
).rejects.toBe(cause);
|
|
expect(output?.handled).toBe(true);
|
|
expect(output?.failed).toBe(true);
|
|
expect(await output?.recoverFormattingFailure(cause)).toBe(false);
|
|
});
|
|
|
|
it("retains the original failure identity and ignores large details on recovery", async () => {
|
|
const chunks: Buffer[] = [];
|
|
const output = createCliResultOutput(
|
|
parseCliOutputArguments([]),
|
|
new Writable({
|
|
write(chunk: Buffer, _encoding, callback) {
|
|
chunks.push(chunk);
|
|
callback();
|
|
},
|
|
}),
|
|
);
|
|
const previousExitCode = process.exitCode;
|
|
try {
|
|
const value = await logCliCommand(silentLogger, "fixture", async () => ({
|
|
code: "target_unavailable",
|
|
category: "unavailable",
|
|
message: "The operation did not complete.",
|
|
retryable: false,
|
|
remediation: { action: "Check the selected path." },
|
|
details: { partial_observation: { text: "captured" } },
|
|
}));
|
|
expect(await output?.write(value, metadata)).toBe(false);
|
|
expect(
|
|
await output?.recoverFormattingFailure(
|
|
new RangeError("Invalid string length"),
|
|
),
|
|
).toBe(true);
|
|
expect(output?.error).toMatchObject({
|
|
code: "resource_constraint",
|
|
details: {
|
|
resource: "memory",
|
|
reported_limits: {
|
|
formatted_characters: null,
|
|
original_failure: {
|
|
code: "target_unavailable",
|
|
category: "unavailable",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
expect(text).toContain("--format json");
|
|
expect(text).not.toContain("partial_observation");
|
|
expect(
|
|
await output?.recoverFormattingFailure(
|
|
new RangeError("Invalid string length"),
|
|
),
|
|
).toBe(false);
|
|
} finally {
|
|
process.exitCode = previousExitCode;
|
|
}
|
|
});
|
|
});
|