89d6a8f6d1
- runPrintMode dereferenced session.sessionManager before checking whether plan.defaultOnStartup was even enabled, crashing partial-session harnesses; the settings check now short-circuits first. - Extended the print-mode session fakes with the settings/sessionManager members every real session provides. - Scoped the rpc-stdin-lock stderr assertion to non-load-notice lines: the adversarial fixture's load failure is now legitimately surfaced (#4954) and mentions the locked stream by design.
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import * as path from "node:path";
|
|
import { isRecord, readJsonl } from "@oh-my-pi/pi-utils";
|
|
|
|
async function expectRpcModeOwnsStdin(mode: "rpc" | "rpc-ui"): Promise<void> {
|
|
const cliPath = path.join(import.meta.dir, "..", "src", "cli.ts");
|
|
const extensionPath = path.join(import.meta.dir, "fixtures", "locked-stdin-reader.ts");
|
|
const child = Bun.spawn(
|
|
[
|
|
"bun",
|
|
cliPath,
|
|
"--extension",
|
|
extensionPath,
|
|
"--mode",
|
|
mode,
|
|
"--provider",
|
|
"anthropic",
|
|
"--model",
|
|
"claude-sonnet-4-5",
|
|
],
|
|
{
|
|
cwd: path.join(import.meta.dir, ".."),
|
|
env: { ...Bun.env, PI_NO_TITLE: "1" },
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
},
|
|
);
|
|
const stderrPromise = new Response(child.stderr).text();
|
|
|
|
child.stdin.write(`${JSON.stringify({ type: "get_state", id: "probe" })}\n`);
|
|
await child.stdin.flush();
|
|
|
|
let stateResponse: Record<string, unknown> | undefined;
|
|
try {
|
|
for await (const frame of readJsonl<unknown>(child.stdout as ReadableStream<Uint8Array>)) {
|
|
if (isRecord(frame) && frame.type === "response" && frame.id === "probe") {
|
|
stateResponse = frame;
|
|
break;
|
|
}
|
|
}
|
|
} finally {
|
|
child.stdin.end();
|
|
child.kill();
|
|
await child.exited.catch(() => {});
|
|
}
|
|
|
|
const stderr = await stderrPromise;
|
|
// The adversarial fixture is EXPECTED to fail loading — RPC claimed stdin
|
|
// first — and its surfaced load notice (#4954) mentions the locked stream.
|
|
// Any OTHER "ReadableStream is locked" line means RPC lost stdin ownership.
|
|
for (const line of stderr.split("\n").filter(l => l.includes("ReadableStream is locked"))) {
|
|
expect(line).toContain("Failed to load extension");
|
|
}
|
|
expect(stateResponse?.success).toBe(true);
|
|
}
|
|
|
|
describe("RPC mode stdin ownership", () => {
|
|
test("rpc claims stdin before extensions can lock its singleton stream", () => expectRpcModeOwnsStdin("rpc"), 30000);
|
|
test(
|
|
"rpc-ui claims stdin before extensions can lock its singleton stream",
|
|
() => expectRpcModeOwnsStdin("rpc-ui"),
|
|
30000,
|
|
);
|
|
});
|