Skip to content

Commit a296cd0

Browse files
committed
test(runtime): complete invoke failure coverage
1 parent 1f79e4d commit a296cd0

3 files changed

Lines changed: 42 additions & 7 deletions

File tree

src/handlers/runtime/invoke/index.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,10 @@ export const createInvokeRuntimeHandler = (core: Core, io: AppIO) =>
6262
try {
6363
await renderTuiAt(path, ctx, core, io);
6464
} catch (error) {
65-
if (
66-
error instanceof TypeError &&
65+
throw error instanceof TypeError &&
6766
error.message === "interactive mode requires a TTY on stdin and stdout"
68-
) {
69-
throw new UsageError(error.message);
70-
}
71-
throw error;
67+
? new UsageError(error.message)
68+
: error;
7269
}
7370
return;
7471
}

src/handlers/runtime/invoke/invoke.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,25 @@ describe("runtime invoke", () => {
320320
expect(core.runtime.calls).toEqual([]);
321321
});
322322

323+
test("classifies the TUI requirement as usage at the handler boundary", async () => {
324+
const core = new TestCoreClient();
325+
const output = captureIO();
326+
const render = spyOn(tui, "renderTuiAt").mockRejectedValue(
327+
new TypeError("interactive mode requires a TTY on stdin and stdout"),
328+
);
329+
330+
try {
331+
const code = await runWithExitCode(async () =>
332+
runCommand(core, output.io, ["runtime", "invoke", "--id", RUNTIME_ID]),
333+
);
334+
335+
expect(code).toBe(ExitCode.USAGE);
336+
expect(core.runtime.calls).toEqual([]);
337+
} finally {
338+
render.mockRestore();
339+
}
340+
});
341+
323342
test("handler deep-links id-only and qualified invokes with encoded path segments", async () => {
324343
const core = new TestCoreClient();
325344
const output = captureIO();

src/handlers/runtime/invoke/response.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
22
import { rm } from "node:fs/promises";
33
import { tmpdir } from "node:os";
44
import { join } from "node:path";
5-
import { PassThrough } from "node:stream";
5+
import { PassThrough, Writable } from "node:stream";
66
import type { RuntimeInvokeResponse } from "../types";
77
import { writeRuntimeInvokeResponse } from "./response";
88

@@ -77,6 +77,25 @@ describe("Runtime invoke response output", () => {
7777
expect(stdout.bytes()).toEqual(Buffer.from([0, 255, 10, 1, 127]));
7878
});
7979

80+
test("sanitizes metadata output failures", async () => {
81+
const stdout = capture();
82+
const stderr = new Writable({
83+
write(_chunk, _encoding, callback) {
84+
callback(new Error("secret metadata sink failure"));
85+
},
86+
}) as unknown as NodeJS.WriteStream;
87+
88+
await expect(
89+
writeRuntimeInvokeResponse(response(), {
90+
stdout: stdout.stream,
91+
stderr,
92+
}),
93+
).rejects.toMatchObject({
94+
message: "response stream failed",
95+
reported: true,
96+
});
97+
});
98+
8099
test("streams exact bytes to a file and leaves stdout empty", async () => {
81100
const file = join(tmpdir(), `runtime-invoke-output-${process.pid}-${files.length}`);
82101
files.push(file);

0 commit comments

Comments
 (0)