import { expect, test, vi } from "vitest"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadPiEnabledModels } from "../src/pi/enabled-models.js"; function fixture(globalValue: unknown, projectValue?: unknown) { const root = mkdtempSync(join(tmpdir(), "tht-enabled-models-")); const agentDir = join(root, "agent"); const harnessDir = join(root, "harness"); mkdirSync(agentDir, { recursive: true }); mkdirSync(join(harnessDir, ".pi"), { recursive: true }); writeFileSync(join(agentDir, "settings.json"), JSON.stringify(globalValue)); if (projectValue !== undefined) { writeFileSync(join(harnessDir, ".pi", "settings.json"), JSON.stringify(projectValue)); } return { root, agentDir, harnessDir }; } test("loads exact global enabledModels in configured order", () => { const f = fixture({ enabledModels: [ "zai/glm-5.2", "deepseek/deepseek-v4-flash", "local-qwen/qwen3.6-35b-a3b", ] }); try { expect(loadPiEnabledModels(f)).toMatchObject({ ids: [ "zai/glm-5.2", "deepseek/deepseek-v4-flash", "local-qwen/qwen3.6-35b-a3b", ], warnings: [], }); } finally { rmSync(f.root, { recursive: true, force: true }); } }); test("uses the configured Pi agent directory when no explicit directory is passed", () => { const f = fixture({ enabledModels: ["zai/glm-5.2"] }); vi.stubEnv("PI_CODING_AGENT_DIR", f.agentDir); try { expect(loadPiEnabledModels({ harnessDir: f.harnessDir })).toMatchObject({ ids: ["zai/glm-5.2"], warnings: [], }); } finally { vi.unstubAllEnvs(); rmSync(f.root, { recursive: true, force: true }); } }); test("project enabledModels overrides global enabledModels", () => { const f = fixture( { enabledModels: ["zai/glm-5.2", "zai/glm-5v-turbo"] }, { enabledModels: ["local-qwen/qwen3.6-35b-a3b"] }, ); try { expect(loadPiEnabledModels(f).ids).toEqual(["local-qwen/qwen3.6-35b-a3b"]); } finally { rmSync(f.root, { recursive: true, force: true }); } }); test("project settings without enabledModels fall back to global settings", () => { const f = fixture({ enabledModels: ["zai/glm-5.2"] }, { theme: "thothii-mono" }); try { expect(loadPiEnabledModels(f).ids).toEqual(["zai/glm-5.2"]); } finally { rmSync(f.root, { recursive: true, force: true }); } }); test("invalid entries are ignored, deduplicated, and reported without their content", () => { const f = fixture({ enabledModels: [ "zai/glm-5.2", "zai/glm-5.2", "glm-5.2", "zai/*", "zai/glm-5.2:high", 7, ] }); try { const result = loadPiEnabledModels(f); expect(result.ids).toEqual(["zai/glm-5.2"]); expect(result.warnings).toHaveLength(4); expect(result.warnings.join(" ")).not.toContain("glm-5.2:high"); } finally { rmSync(f.root, { recursive: true, force: true }); } }); test.each([ ["missing field", {}], ["empty field", { enabledModels: [] }], ["wrong type", { enabledModels: "zai/glm-5.2" }], ])("%s fails closed", (_name, settings) => { const f = fixture(settings); try { const result = loadPiEnabledModels(f); expect(result.ids).toEqual([]); expect(result.warnings.length).toBeGreaterThan(0); } finally { rmSync(f.root, { recursive: true, force: true }); } }); test("malformed project settings fail closed instead of exposing global models", () => { const f = fixture({ enabledModels: ["zai/glm-5.2"] }); writeFileSync(join(f.harnessDir, ".pi", "settings.json"), "{"); try { const result = loadPiEnabledModels(f); expect(result.ids).toEqual([]); expect(result.warnings).toHaveLength(1); } finally { rmSync(f.root, { recursive: true, force: true }); } }); test("missing global settings fail closed", () => { const f = fixture({ enabledModels: ["zai/glm-5.2"] }); rmSync(join(f.agentDir, "settings.json")); try { const result = loadPiEnabledModels(f); expect(result.ids).toEqual([]); expect(result.warnings).toHaveLength(1); } finally { rmSync(f.root, { recursive: true, force: true }); } });