test(pi-claude-cli): cover async CLI validation probes

Adds vitest coverage for validateCliPresenceAsync and validateCliAuthAsync,
verifying success paths, spawn-error paths, non-zero exit codes, and that
auth failures emit the expected warning. Pairs with the async/memoized
validation refactor in f7df0d4e3 that unblocks the dashboard event loop
on every chat send.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-30 10:01:53 -07:00
parent 3cd2b7099e
commit f11860619f
2 changed files with 95 additions and 0 deletions

View File

@@ -47,6 +47,8 @@ import {
captureStderr,
validateCliPresence,
validateCliAuth,
validateCliPresenceAsync,
validateCliAuthAsync,
forceKillProcess,
registerProcess,
killAllProcesses,
@@ -407,6 +409,94 @@ describe("validateCliAuth", () => {
});
});
describe("validateCliPresenceAsync", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("resolves ok=true when claude --version exits 0", async () => {
const EventEmitter = require("node:events");
(spawn as any).mockImplementationOnce(() => {
const proc = new EventEmitter();
proc.kill = vi.fn();
setImmediate(() => proc.emit("exit", 0));
return proc;
});
const result = await validateCliPresenceAsync();
expect(result).toEqual({ ok: true });
const args = (spawn as any).mock.calls[0][1] as string[];
expect(args).toEqual(["--version"]);
});
it("resolves ok=false with install message when spawn errors", async () => {
const EventEmitter = require("node:events");
(spawn as any).mockImplementationOnce(() => {
const proc = new EventEmitter();
proc.kill = vi.fn();
setImmediate(() => proc.emit("error", new Error("ENOENT")));
return proc;
});
const result = await validateCliPresenceAsync();
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.message).toContain("Claude Code CLI not found");
expect(result.error.message).toContain("npm install");
}
});
it("resolves ok=false when claude --version exits non-zero", async () => {
const EventEmitter = require("node:events");
(spawn as any).mockImplementationOnce(() => {
const proc = new EventEmitter();
proc.kill = vi.fn();
setImmediate(() => proc.emit("exit", 1));
return proc;
});
const result = await validateCliPresenceAsync();
expect(result.ok).toBe(false);
});
});
describe("validateCliAuthAsync", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("resolves true when claude auth status exits 0", async () => {
const EventEmitter = require("node:events");
(spawn as any).mockImplementationOnce(() => {
const proc = new EventEmitter();
proc.kill = vi.fn();
setImmediate(() => proc.emit("exit", 0));
return proc;
});
expect(await validateCliAuthAsync()).toBe(true);
const args = (spawn as any).mock.calls[0][1] as string[];
expect(args).toEqual(["auth", "status"]);
});
it("resolves false and warns when claude auth status fails", async () => {
const EventEmitter = require("node:events");
(spawn as any).mockImplementationOnce(() => {
const proc = new EventEmitter();
proc.kill = vi.fn();
setImmediate(() => proc.emit("exit", 1));
return proc;
});
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
expect(await validateCliAuthAsync()).toBe(false);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining("not authenticated"),
);
warnSpy.mockRestore();
});
});
describe("CLI flags", () => {
beforeEach(() => {
vi.clearAllMocks();