import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { __setModelCatalogImportForTest, loadModelCatalog, resetModelCatalogCacheForTest, } from "./model-catalog.js"; type PiSdkModule = typeof import("./pi-model-discovery.js"); vi.mock("./models-config.js", () => ({ ensureOpenClawModelsJson: vi.fn().mockResolvedValue({ agentDir: "/tmp", wrote: false }), })); vi.mock("./agent-paths.js", () => ({ resolveOpenClawAgentDir: () => "/tmp/openclaw", })); describe("loadModelCatalog", () => { beforeEach(() => { resetModelCatalogCacheForTest(); }); afterEach(() => { __setModelCatalogImportForTest(); resetModelCatalogCacheForTest(); vi.restoreAllMocks(); }); it("retries after import failure without poisoning the cache", async () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); let call = 0; __setModelCatalogImportForTest(async () => { call += 1; if (call === 1) { throw new Error("boom"); } return { AuthStorage: class {}, ModelRegistry: class { getAll() { return [{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }]; } }, } as unknown as PiSdkModule; }); const cfg = {} as OpenClawConfig; const first = await loadModelCatalog({ config: cfg }); expect(first).toEqual([]); const second = await loadModelCatalog({ config: cfg }); expect(second).toEqual([{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }]); expect(call).toBe(2); expect(warnSpy).toHaveBeenCalledTimes(1); }); it("returns partial results on discovery errors", async () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); __setModelCatalogImportForTest( async () => ({ AuthStorage: class {}, ModelRegistry: class { getAll() { return [ { id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }, { get id() { throw new Error("boom"); }, provider: "openai", name: "bad", }, ]; } }, }) as unknown as PiSdkModule, ); const result = await loadModelCatalog({ config: {} as OpenClawConfig }); expect(result).toEqual([{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }]); expect(warnSpy).toHaveBeenCalledTimes(1); }); it("adds openai-codex/gpt-5.3-codex-spark when base gpt-5.3-codex exists", async () => { __setModelCatalogImportForTest( async () => ({ AuthStorage: class {}, ModelRegistry: class { getAll() { return [ { id: "gpt-5.3-codex", provider: "openai-codex", name: "GPT-5.3 Codex", reasoning: true, contextWindow: 200000, input: ["text"], }, { id: "gpt-5.2-codex", provider: "openai-codex", name: "GPT-5.2 Codex", }, ]; } }, }) as unknown as PiSdkModule, ); const result = await loadModelCatalog({ config: {} as OpenClawConfig }); expect(result).toContainEqual( expect.objectContaining({ provider: "openai-codex", id: "gpt-5.3-codex-spark", }), ); const spark = result.find((entry) => entry.id === "gpt-5.3-codex-spark"); expect(spark?.name).toBe("gpt-5.3-codex-spark"); expect(spark?.reasoning).toBe(true); }); });