import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { ProviderFactory } from "@/providers/provider-factory.js"; import { ClaudeProvider } from "@/providers/claude-provider.js"; import { CodexProvider } from "@/providers/codex-provider.js"; describe("provider-factory.ts", () => { let consoleSpy: any; beforeEach(() => { consoleSpy = { warn: vi.spyOn(console, "warn").mockImplementation(() => {}), }; }); afterEach(() => { consoleSpy.warn.mockRestore(); }); describe("getProviderForModel", () => { describe("OpenAI/Codex models (gpt-*)", () => { it("should return CodexProvider for gpt-5.2", () => { const provider = ProviderFactory.getProviderForModel("gpt-5.2"); expect(provider).toBeInstanceOf(CodexProvider); }); it("should return CodexProvider for gpt-5.1-codex", () => { const provider = ProviderFactory.getProviderForModel("gpt-5.1-codex"); expect(provider).toBeInstanceOf(CodexProvider); }); it("should return CodexProvider for gpt-4", () => { const provider = ProviderFactory.getProviderForModel("gpt-4"); expect(provider).toBeInstanceOf(CodexProvider); }); it("should be case-insensitive for gpt models", () => { const provider1 = ProviderFactory.getProviderForModel("GPT-5.2"); const provider2 = ProviderFactory.getProviderForModel("Gpt-5.1"); expect(provider1).toBeInstanceOf(CodexProvider); expect(provider2).toBeInstanceOf(CodexProvider); }); }); describe("Unsupported o-series models", () => { it("should default to ClaudeProvider for o1 (not supported by Codex CLI)", () => { const provider = ProviderFactory.getProviderForModel("o1"); expect(provider).toBeInstanceOf(ClaudeProvider); expect(consoleSpy.warn).toHaveBeenCalled(); }); it("should default to ClaudeProvider for o3", () => { const provider = ProviderFactory.getProviderForModel("o3"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should default to ClaudeProvider for o1-mini", () => { const provider = ProviderFactory.getProviderForModel("o1-mini"); expect(provider).toBeInstanceOf(ClaudeProvider); }); }); describe("Claude models (claude-* prefix)", () => { it("should return ClaudeProvider for claude-opus-4-5-20251101", () => { const provider = ProviderFactory.getProviderForModel( "claude-opus-4-5-20251101" ); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should return ClaudeProvider for claude-sonnet-4-20250514", () => { const provider = ProviderFactory.getProviderForModel( "claude-sonnet-4-20250514" ); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should return ClaudeProvider for claude-haiku-4-5", () => { const provider = ProviderFactory.getProviderForModel("claude-haiku-4-5"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should be case-insensitive for claude models", () => { const provider = ProviderFactory.getProviderForModel( "CLAUDE-OPUS-4-5-20251101" ); expect(provider).toBeInstanceOf(ClaudeProvider); }); }); describe("Claude aliases", () => { it("should return ClaudeProvider for 'haiku'", () => { const provider = ProviderFactory.getProviderForModel("haiku"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should return ClaudeProvider for 'sonnet'", () => { const provider = ProviderFactory.getProviderForModel("sonnet"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should return ClaudeProvider for 'opus'", () => { const provider = ProviderFactory.getProviderForModel("opus"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should be case-insensitive for aliases", () => { const provider1 = ProviderFactory.getProviderForModel("HAIKU"); const provider2 = ProviderFactory.getProviderForModel("Sonnet"); const provider3 = ProviderFactory.getProviderForModel("Opus"); expect(provider1).toBeInstanceOf(ClaudeProvider); expect(provider2).toBeInstanceOf(ClaudeProvider); expect(provider3).toBeInstanceOf(ClaudeProvider); }); }); describe("Unknown models", () => { it("should default to ClaudeProvider for unknown model", () => { const provider = ProviderFactory.getProviderForModel("unknown-model-123"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should warn when defaulting to Claude", () => { ProviderFactory.getProviderForModel("random-model"); expect(consoleSpy.warn).toHaveBeenCalledWith( expect.stringContaining("Unknown model prefix") ); expect(consoleSpy.warn).toHaveBeenCalledWith( expect.stringContaining("random-model") ); expect(consoleSpy.warn).toHaveBeenCalledWith( expect.stringContaining("defaulting to Claude") ); }); it("should handle empty string", () => { const provider = ProviderFactory.getProviderForModel(""); expect(provider).toBeInstanceOf(ClaudeProvider); expect(consoleSpy.warn).toHaveBeenCalled(); }); }); }); describe("getAllProviders", () => { it("should return array of all providers", () => { const providers = ProviderFactory.getAllProviders(); expect(Array.isArray(providers)).toBe(true); }); it("should include ClaudeProvider", () => { const providers = ProviderFactory.getAllProviders(); const hasClaudeProvider = providers.some( (p) => p instanceof ClaudeProvider ); expect(hasClaudeProvider).toBe(true); }); it("should include CodexProvider", () => { const providers = ProviderFactory.getAllProviders(); const hasCodexProvider = providers.some((p) => p instanceof CodexProvider); expect(hasCodexProvider).toBe(true); }); it("should return exactly 2 providers", () => { const providers = ProviderFactory.getAllProviders(); expect(providers).toHaveLength(2); }); it("should create new instances each time", () => { const providers1 = ProviderFactory.getAllProviders(); const providers2 = ProviderFactory.getAllProviders(); expect(providers1[0]).not.toBe(providers2[0]); expect(providers1[1]).not.toBe(providers2[1]); }); }); describe("checkAllProviders", () => { it("should return installation status for all providers", async () => { const statuses = await ProviderFactory.checkAllProviders(); expect(statuses).toHaveProperty("claude"); expect(statuses).toHaveProperty("codex"); }); it("should call detectInstallation on each provider", async () => { const statuses = await ProviderFactory.checkAllProviders(); expect(statuses.claude).toHaveProperty("installed"); expect(statuses.codex).toHaveProperty("installed"); }); it("should return correct provider names as keys", async () => { const statuses = await ProviderFactory.checkAllProviders(); const keys = Object.keys(statuses); expect(keys).toContain("claude"); expect(keys).toContain("codex"); expect(keys).toHaveLength(2); }); }); describe("getProviderByName", () => { it("should return ClaudeProvider for 'claude'", () => { const provider = ProviderFactory.getProviderByName("claude"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should return ClaudeProvider for 'anthropic'", () => { const provider = ProviderFactory.getProviderByName("anthropic"); expect(provider).toBeInstanceOf(ClaudeProvider); }); it("should return CodexProvider for 'codex'", () => { const provider = ProviderFactory.getProviderByName("codex"); expect(provider).toBeInstanceOf(CodexProvider); }); it("should return CodexProvider for 'openai'", () => { const provider = ProviderFactory.getProviderByName("openai"); expect(provider).toBeInstanceOf(CodexProvider); }); it("should be case-insensitive", () => { const provider1 = ProviderFactory.getProviderByName("CLAUDE"); const provider2 = ProviderFactory.getProviderByName("Codex"); const provider3 = ProviderFactory.getProviderByName("ANTHROPIC"); expect(provider1).toBeInstanceOf(ClaudeProvider); expect(provider2).toBeInstanceOf(CodexProvider); expect(provider3).toBeInstanceOf(ClaudeProvider); }); it("should return null for unknown provider", () => { const provider = ProviderFactory.getProviderByName("unknown"); expect(provider).toBeNull(); }); it("should return null for empty string", () => { const provider = ProviderFactory.getProviderByName(""); expect(provider).toBeNull(); }); it("should create new instance each time", () => { const provider1 = ProviderFactory.getProviderByName("claude"); const provider2 = ProviderFactory.getProviderByName("claude"); expect(provider1).not.toBe(provider2); expect(provider1).toBeInstanceOf(ClaudeProvider); expect(provider2).toBeInstanceOf(ClaudeProvider); }); }); describe("getAllAvailableModels", () => { it("should return array of models", () => { const models = ProviderFactory.getAllAvailableModels(); expect(Array.isArray(models)).toBe(true); }); it("should include models from all providers", () => { const models = ProviderFactory.getAllAvailableModels(); expect(models.length).toBeGreaterThan(0); }); it("should return models with required fields", () => { const models = ProviderFactory.getAllAvailableModels(); models.forEach((model) => { expect(model).toHaveProperty("id"); expect(model).toHaveProperty("name"); expect(typeof model.id).toBe("string"); expect(typeof model.name).toBe("string"); }); }); it("should aggregate models from both Claude and Codex", () => { const models = ProviderFactory.getAllAvailableModels(); // Claude models should include claude-* in their IDs const hasClaudeModels = models.some((m) => m.id.toLowerCase().includes("claude") ); // Codex models should include gpt-* in their IDs const hasCodexModels = models.some((m) => m.id.toLowerCase().includes("gpt") ); expect(hasClaudeModels).toBe(true); expect(hasCodexModels).toBe(true); }); }); });