import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { resolveBackendName, createBackend } from "../../src/backends/registry.js"; import type { BackendAdapter, BackendAdapterConfig } from "../../src/backends/types.js"; const defaultAdapterConfig: BackendAdapterConfig = { cliPath: "/usr/bin/claude", workingDir: "/tmp", queryTimeoutMs: 30000, allowedTools: [], maxTurns: 25, }; describe("Startup validation flow", () => { describe("valid backend creation and validation", () => { it("should create a claude backend and validate successfully when binary is accessible", async () => { const backend = createBackend("claude", defaultAdapterConfig); expect(backend.name()).toBe("claude"); // validate() checks fs access — we test the integration via the registry expect(typeof backend.validate).toBe("function"); }); it("should create each valid backend type", () => { const names = ["claude", "codex", "gemini", "opencode"] as const; for (const name of names) { const backend = createBackend(name, defaultAdapterConfig); expect(backend.name()).toBe(name); } }); }); describe("invalid backend name", () => { it("should throw a descriptive error for an invalid backend name", () => { expect(() => resolveBackendName("invalid-backend")).toThrow( 'Invalid backend name "invalid-backend". Valid options are: claude, codex, gemini, opencode', ); }); it("should throw for empty string backend name", () => { expect(() => resolveBackendName("")).toThrow( 'Invalid backend name "". Valid options are: claude, codex, gemini, opencode', ); }); it("should default to claude when backend name is undefined", () => { expect(resolveBackendName(undefined)).toBe("claude"); }); }); describe("missing CLI binary (validate returns false)", () => { it("should return false from validate() when CLI path does not exist", async () => { const backend = createBackend("claude", { ...defaultAdapterConfig, cliPath: "/nonexistent/path/to/cli", }); const isValid = await backend.validate(); expect(isValid).toBe(false); }); it("should return false from validate() for codex backend with missing binary", async () => { const backend = createBackend("codex", { ...defaultAdapterConfig, cliPath: "/nonexistent/codex-binary", }); const isValid = await backend.validate(); expect(isValid).toBe(false); }); }); describe("startup wiring simulation", () => { let exitSpy: ReturnType; beforeEach(() => { exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as any); }); afterEach(() => { exitSpy.mockRestore(); }); it("should exit with code 1 when backend validation fails", async () => { const backendName = resolveBackendName("claude"); const backend = createBackend(backendName, { ...defaultAdapterConfig, cliPath: "/nonexistent/binary", }); const isValid = await backend.validate(); if (!isValid) { process.exit(1); } expect(isValid).toBe(false); expect(exitSpy).toHaveBeenCalledWith(1); }); it("should not exit when backend validation succeeds", async () => { // Create a mock backend that validates successfully const mockBackend: BackendAdapter = { name: () => "claude", execute: vi.fn(), validate: vi.fn().mockResolvedValue(true), }; const isValid = await mockBackend.validate(); if (!isValid) { process.exit(1); } expect(isValid).toBe(true); expect(exitSpy).not.toHaveBeenCalled(); }); }); });