import { describe, expect, test } from "bun:test"; import * as path from "node:path"; import { RpcClient } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-client"; const MOCK_AGENT = path.join(import.meta.dir, "fixtures", "mock-rpc-agent.ts"); describe("RpcClient lifecycle (issue #4079 B)", () => { test("start() succeeds a second time after stop() on the same instance", async () => { using client = new RpcClient({ cliPath: MOCK_AGENT, }); // First lifecycle: start + stop. await client.start(); client.stop(); // Second start on the same instance must NOT reuse the aborted // controller from the previous stop(). Before the fix, this rejected // with "Agent process exited before ready" because the JSONL reader // short-circuited on the pre-aborted signal. await client.start(); client.stop(); }, 20000); test("start() may be retried after a failed start (child is cleaned up on failure)", async () => { using client = new RpcClient({ cliPath: path.join(import.meta.dir, "..", "src", "cli.ts"), cwd: path.join(import.meta.dir, ".."), provider: "__missing_provider__", model: "claude-sonnet-4-5", env: { PI_NO_TITLE: "1" }, }); await expect(client.start()).rejects.toThrow(/Unknown provider.*__missing_provider__/); // Before the fix, #process stayed set after the failed spawn so the // second start() rejected with "Client already started". Post-fix, // state is cleared and the second attempt fails with the same // legitimate startup error. await expect(client.start()).rejects.toThrow(/Unknown provider.*__missing_provider__/); }, 30000); });