import { describe, expect, test, mock, beforeEach, afterEach } from "bun:test" import { ANTIGRAVITY_PROVIDER_CONFIG, getPluginNameWithVersion, fetchNpmDistTags, generateOmoConfig } from "./config-manager" import type { InstallConfig } from "./types" describe("getPluginNameWithVersion", () => { const originalFetch = globalThis.fetch afterEach(() => { globalThis.fetch = originalFetch }) test("returns @latest when current version matches latest tag", async () => { // #given npm dist-tags with latest=2.14.0 globalThis.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }), } as Response) ) as unknown as typeof fetch // #when current version is 2.14.0 const result = await getPluginNameWithVersion("2.14.0") // #then should use @latest tag expect(result).toBe("oh-my-opencode@latest") }) test("returns @beta when current version matches beta tag", async () => { // #given npm dist-tags with beta=3.0.0-beta.3 globalThis.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }), } as Response) ) as unknown as typeof fetch // #when current version is 3.0.0-beta.3 const result = await getPluginNameWithVersion("3.0.0-beta.3") // #then should use @beta tag expect(result).toBe("oh-my-opencode@beta") }) test("returns @next when current version matches next tag", async () => { // #given npm dist-tags with next=3.1.0-next.1 globalThis.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3", next: "3.1.0-next.1" }), } as Response) ) as unknown as typeof fetch // #when current version is 3.1.0-next.1 const result = await getPluginNameWithVersion("3.1.0-next.1") // #then should use @next tag expect(result).toBe("oh-my-opencode@next") }) test("returns pinned version when no tag matches", async () => { // #given npm dist-tags with beta=3.0.0-beta.3 globalThis.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }), } as Response) ) as unknown as typeof fetch // #when current version is old beta 3.0.0-beta.2 const result = await getPluginNameWithVersion("3.0.0-beta.2") // #then should pin to specific version expect(result).toBe("oh-my-opencode@3.0.0-beta.2") }) test("returns pinned version when fetch fails", async () => { // #given network failure globalThis.fetch = mock(() => Promise.reject(new Error("Network error"))) as unknown as typeof fetch // #when current version is 3.0.0-beta.3 const result = await getPluginNameWithVersion("3.0.0-beta.3") // #then should fall back to pinned version expect(result).toBe("oh-my-opencode@3.0.0-beta.3") }) test("returns pinned version when npm returns non-ok response", async () => { // #given npm returns 404 globalThis.fetch = mock(() => Promise.resolve({ ok: false, status: 404, } as Response) ) as unknown as typeof fetch // #when current version is 2.14.0 const result = await getPluginNameWithVersion("2.14.0") // #then should fall back to pinned version expect(result).toBe("oh-my-opencode@2.14.0") }) test("prioritizes latest over other tags when version matches multiple", async () => { // #given version matches both latest and beta (during release promotion) globalThis.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ beta: "3.0.0", latest: "3.0.0", next: "3.1.0-alpha.1" }), } as Response) ) as unknown as typeof fetch // #when current version matches both const result = await getPluginNameWithVersion("3.0.0") // #then should prioritize @latest expect(result).toBe("oh-my-opencode@latest") }) }) describe("fetchNpmDistTags", () => { const originalFetch = globalThis.fetch afterEach(() => { globalThis.fetch = originalFetch }) test("returns dist-tags on success", async () => { // #given npm returns dist-tags globalThis.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ latest: "2.14.0", beta: "3.0.0-beta.3" }), } as Response) ) as unknown as typeof fetch // #when fetching dist-tags const result = await fetchNpmDistTags("oh-my-opencode") // #then should return the tags expect(result).toEqual({ latest: "2.14.0", beta: "3.0.0-beta.3" }) }) test("returns null on network failure", async () => { // #given network failure globalThis.fetch = mock(() => Promise.reject(new Error("Network error"))) as unknown as typeof fetch // #when fetching dist-tags const result = await fetchNpmDistTags("oh-my-opencode") // #then should return null expect(result).toBeNull() }) test("returns null on non-ok response", async () => { // #given npm returns 404 globalThis.fetch = mock(() => Promise.resolve({ ok: false, status: 404, } as Response) ) as unknown as typeof fetch // #when fetching dist-tags const result = await fetchNpmDistTags("oh-my-opencode") // #then should return null expect(result).toBeNull() }) }) describe("config-manager ANTIGRAVITY_PROVIDER_CONFIG", () => { test("Gemini models include full spec (limit + modalities)", () => { const google = (ANTIGRAVITY_PROVIDER_CONFIG as any).google expect(google).toBeTruthy() const models = google.models as Record expect(models).toBeTruthy() const required = [ "antigravity-gemini-3-pro-high", "antigravity-gemini-3-pro-low", "antigravity-gemini-3-flash", ] for (const key of required) { const model = models[key] expect(model).toBeTruthy() expect(typeof model.name).toBe("string") expect(model.name.includes("(Antigravity)")).toBe(true) expect(model.limit).toBeTruthy() expect(typeof model.limit.context).toBe("number") expect(typeof model.limit.output).toBe("number") expect(model.modalities).toBeTruthy() expect(Array.isArray(model.modalities.input)).toBe(true) expect(Array.isArray(model.modalities.output)).toBe(true) } }) }) describe("generateOmoConfig - GitHub Copilot fallback", () => { test("frontend-ui-ux-engineer uses Copilot when no native providers", () => { // #given user has only Copilot (no Claude, ChatGPT, Gemini) const config: InstallConfig = { hasClaude: false, isMax20: false, hasChatGPT: false, hasGemini: false, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then frontend-ui-ux-engineer should use Copilot Gemini const agents = result.agents as Record expect(agents["frontend-ui-ux-engineer"]?.model).toBe("github-copilot/gemini-3-pro-preview") }) test("document-writer uses Copilot when no native providers", () => { // #given user has only Copilot const config: InstallConfig = { hasClaude: false, isMax20: false, hasChatGPT: false, hasGemini: false, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then document-writer should use Copilot Gemini Flash const agents = result.agents as Record expect(agents["document-writer"]?.model).toBe("github-copilot/gemini-3-flash-preview") }) test("multimodal-looker uses Copilot when no native providers", () => { // #given user has only Copilot const config: InstallConfig = { hasClaude: false, isMax20: false, hasChatGPT: false, hasGemini: false, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then multimodal-looker should use Copilot Gemini Flash const agents = result.agents as Record expect(agents["multimodal-looker"]?.model).toBe("github-copilot/gemini-3-flash-preview") }) test("explore uses Copilot grok-code when no native providers", () => { // #given user has only Copilot const config: InstallConfig = { hasClaude: false, isMax20: false, hasChatGPT: false, hasGemini: false, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then explore should use Copilot Grok const agents = result.agents as Record expect(agents["explore"]?.model).toBe("github-copilot/grok-code-fast-1") }) test("native Gemini takes priority over Copilot for frontend-ui-ux-engineer", () => { // #given user has both Gemini and Copilot const config: InstallConfig = { hasClaude: false, isMax20: false, hasChatGPT: false, hasGemini: true, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then native Gemini should be used (NOT Copilot) const agents = result.agents as Record expect(agents["frontend-ui-ux-engineer"]?.model).toBe("google/antigravity-gemini-3-pro-high") }) test("native Claude takes priority over Copilot for frontend-ui-ux-engineer", () => { // #given user has Claude and Copilot but no Gemini const config: InstallConfig = { hasClaude: true, isMax20: false, hasChatGPT: false, hasGemini: false, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then native Claude should be used (NOT Copilot) const agents = result.agents as Record expect(agents["frontend-ui-ux-engineer"]?.model).toBe("anthropic/claude-opus-4-5") }) test("categories use Copilot models when no native Gemini", () => { // #given user has Copilot but no Gemini const config: InstallConfig = { hasClaude: false, isMax20: false, hasChatGPT: false, hasGemini: false, hasCopilot: true, } // #when generating config const result = generateOmoConfig(config) // #then categories should use Copilot models const categories = result.categories as Record expect(categories?.["visual-engineering"]?.model).toBe("github-copilot/gemini-3-pro-preview") expect(categories?.["artistry"]?.model).toBe("github-copilot/gemini-3-pro-preview") expect(categories?.["writing"]?.model).toBe("github-copilot/gemini-3-flash-preview") }) })