import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { runConfigCommand } from "@oh-my-pi/pi-coding-agent/cli/config-cli"; import { resetSettingsForTest } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; let testAgentDir = ""; const originalAgentDir = process.env.PI_CODING_AGENT_DIR; const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); beforeEach(async () => { resetSettingsForTest(); testAgentDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-config-cli-")); setAgentDir(testAgentDir); }); afterEach(async () => { vi.restoreAllMocks(); resetSettingsForTest(); if (originalAgentDir) { setAgentDir(originalAgentDir); } else { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } await fs.rm(testAgentDir, { recursive: true, force: true }); }); describe("config CLI schema coverage", () => { it("renders record settings as JSON and with record type in text output", async () => { const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await runConfigCommand({ action: "list", flags: {} }); const lines = logSpy.mock.calls.map(call => String(call[0] ?? "")); const plainLines = lines.map(line => Bun.stripANSI(line)); const modelRolesLine = plainLines.find(line => line.includes("modelRoles =")); expect(modelRolesLine).toBeDefined(); const plainModelRolesLine = String(modelRolesLine); expect(plainModelRolesLine).toContain("modelRoles ="); expect(plainModelRolesLine).toContain("(record)"); expect(plainModelRolesLine).toContain("{"); expect(plainModelRolesLine).toContain("}"); expect(plainModelRolesLine).not.toContain("[object Object]"); }); it("sets and gets record settings as JSON objects", async () => { const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); const recordValue = '{"default":"claude-opus-4-6"}'; await runConfigCommand({ action: "set", key: "modelRoles", value: recordValue, flags: { json: true } }); await runConfigCommand({ action: "get", key: "modelRoles", flags: { json: true } }); const payload = logSpy.mock.calls.at(-1)?.[0]; expect(typeof payload).toBe("string"); const parsed = JSON.parse(String(payload)) as { key: string; value: unknown; type: string }; expect(parsed.key).toBe("modelRoles"); expect(parsed.type).toBe("record"); expect(parsed.value).toEqual({ default: "claude-opus-4-6" }); }); it("sets and gets array settings as JSON arrays", async () => { const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); const arrayValue = '["claude-opus-4-6","gpt-5.3-codex"]'; await runConfigCommand({ action: "set", key: "enabledModels", value: arrayValue, flags: { json: true } }); await runConfigCommand({ action: "get", key: "enabledModels", flags: { json: true } }); const payload = logSpy.mock.calls.at(-1)?.[0]; expect(typeof payload).toBe("string"); const parsed = JSON.parse(String(payload)) as { key: string; value: unknown; type: string }; expect(parsed.key).toBe("enabledModels"); expect(parsed.type).toBe("array"); expect(parsed.value).toEqual(["claude-opus-4-6", "gpt-5.3-codex"]); }); it("sets numeric idle compaction settings from CLI values", async () => { const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await runConfigCommand({ action: "set", key: "compaction.idleThresholdTokens", value: "300000", flags: { json: true }, }); await runConfigCommand({ action: "set", key: "compaction.idleTimeoutSeconds", value: "600", flags: { json: true }, }); await runConfigCommand({ action: "get", key: "compaction.idleThresholdTokens", flags: { json: true } }); await runConfigCommand({ action: "get", key: "compaction.idleTimeoutSeconds", flags: { json: true } }); const thresholdPayload = logSpy.mock.calls.at(-2)?.[0]; const timeoutPayload = logSpy.mock.calls.at(-1)?.[0]; expect(typeof thresholdPayload).toBe("string"); expect(typeof timeoutPayload).toBe("string"); expect(JSON.parse(String(thresholdPayload))).toMatchObject({ key: "compaction.idleThresholdTokens", type: "number", value: 300000, }); expect(JSON.parse(String(timeoutPayload))).toMatchObject({ key: "compaction.idleTimeoutSeconds", type: "number", value: 600, }); }); });