From 8954f7719c2caa04f6ecd70f07b9aa43d75a68fa Mon Sep 17 00:00:00 2001 From: mneves75 Date: Tue, 6 Jan 2026 11:35:35 -0300 Subject: [PATCH] Test: cover z.ai normalization --- src/agents/model-selection.test.ts | 46 ++++++++- src/commands/models.list.test.ts | 159 +++++++++++++++++++++++++++++ src/commands/models.set.test.ts | 103 +++++++++++++++++++ 3 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 src/commands/models.list.test.ts create mode 100644 src/commands/models.set.test.ts diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index 5b107916d..7e87c2916 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -73,9 +73,37 @@ describe("resolveConfiguredModelRef", () => { }); }); - it("still resolves legacy agent.model string", () => { + it("normalizes z.ai provider in agent.model", () => { const cfg = { - agent: { model: "openai/gpt-4.1-mini" }, + agent: { model: "z.ai/glm-4.7" }, + } satisfies ClawdbotConfig; + + const resolved = resolveConfiguredModelRef({ + cfg, + defaultProvider: DEFAULT_PROVIDER, + defaultModel: DEFAULT_MODEL, + }); + + expect(resolved).toEqual({ provider: "zai", model: "glm-4.7" }); + }); + + it("normalizes z-ai provider in agent.model", () => { + const cfg = { + agent: { model: "z-ai/glm-4.7" }, + } satisfies ClawdbotConfig; + + const resolved = resolveConfiguredModelRef({ + cfg, + defaultProvider: DEFAULT_PROVIDER, + defaultModel: DEFAULT_MODEL, + }); + + expect(resolved).toEqual({ provider: "zai", model: "glm-4.7" }); + }); + + it("normalizes provider casing in agent.model", () => { + const cfg = { + agent: { model: "OpenAI/gpt-4.1-mini" }, } satisfies ClawdbotConfig; const resolved = resolveConfiguredModelRef({ @@ -86,4 +114,18 @@ describe("resolveConfiguredModelRef", () => { expect(resolved).toEqual({ provider: "openai", model: "gpt-4.1-mini" }); }); + + it("normalizes z.ai casing in agent.model", () => { + const cfg = { + agent: { model: "Z.AI/glm-4.7" }, + } satisfies ClawdbotConfig; + + const resolved = resolveConfiguredModelRef({ + cfg, + defaultProvider: DEFAULT_PROVIDER, + defaultModel: DEFAULT_MODEL, + }); + + expect(resolved).toEqual({ provider: "zai", model: "glm-4.7" }); + }); }); diff --git a/src/commands/models.list.test.ts b/src/commands/models.list.test.ts new file mode 100644 index 000000000..15181df7a --- /dev/null +++ b/src/commands/models.list.test.ts @@ -0,0 +1,159 @@ +import { describe, expect, it, vi } from "vitest"; + +const loadConfig = vi.fn(); +const ensureClawdbotModelsJson = vi.fn().mockResolvedValue(undefined); +const resolveClawdbotAgentDir = vi.fn().mockReturnValue("/tmp/clawdbot-agent"); +const ensureAuthProfileStore = vi.fn().mockReturnValue({}); +const listProfilesForProvider = vi.fn().mockReturnValue([]); +const resolveEnvApiKey = vi.fn().mockReturnValue(undefined); +const getCustomProviderApiKey = vi.fn().mockReturnValue(undefined); +const discoverAuthStorage = vi.fn().mockReturnValue({}); +const discoverModels = vi.fn(); + +vi.mock("../config/config.js", () => ({ + CONFIG_PATH_CLAWDBOT: "/tmp/clawdbot.json", + loadConfig, +})); + +vi.mock("../agents/models-config.js", () => ({ + ensureClawdbotModelsJson, +})); + +vi.mock("../agents/agent-paths.js", () => ({ + resolveClawdbotAgentDir, +})); + +vi.mock("../agents/auth-profiles.js", () => ({ + ensureAuthProfileStore, + listProfilesForProvider, +})); + +vi.mock("../agents/model-auth.js", () => ({ + resolveEnvApiKey, + getCustomProviderApiKey, +})); + +vi.mock("@mariozechner/pi-coding-agent", () => ({ + discoverAuthStorage, + discoverModels, +})); + +function makeRuntime() { + return { + log: vi.fn(), + error: vi.fn(), + }; +} + +describe("models list/status", () => { + it("models status resolves z.ai alias to canonical zai", async () => { + loadConfig.mockReturnValue({ agent: { model: "z.ai/glm-4.7" } }); + const runtime = makeRuntime(); + + const { modelsStatusCommand } = await import("./models/list.js"); + await modelsStatusCommand({ json: true }, runtime); + + expect(runtime.log).toHaveBeenCalledTimes(1); + const payload = JSON.parse(String(runtime.log.mock.calls[0]?.[0])); + expect(payload.resolvedDefault).toBe("zai/glm-4.7"); + }); + + it("models status plain outputs canonical zai model", async () => { + loadConfig.mockReturnValue({ agent: { model: "z.ai/glm-4.7" } }); + const runtime = makeRuntime(); + + const { modelsStatusCommand } = await import("./models/list.js"); + await modelsStatusCommand({ plain: true }, runtime); + + expect(runtime.log).toHaveBeenCalledTimes(1); + expect(runtime.log.mock.calls[0]?.[0]).toBe("zai/glm-4.7"); + }); + + it("models list outputs canonical zai key for configured z.ai model", async () => { + loadConfig.mockReturnValue({ agent: { model: "z.ai/glm-4.7" } }); + const runtime = makeRuntime(); + + const model = { + provider: "zai", + id: "glm-4.7", + name: "GLM-4.7", + input: ["text"], + baseUrl: "https://api.z.ai/v1", + contextWindow: 128000, + }; + + discoverModels.mockReturnValue({ + getAll: () => [model], + getAvailable: () => [model], + }); + + const { modelsListCommand } = await import("./models/list.js"); + await modelsListCommand({ json: true }, runtime); + + expect(runtime.log).toHaveBeenCalledTimes(1); + const payload = JSON.parse(String(runtime.log.mock.calls[0]?.[0])); + expect(payload.models[0]?.key).toBe("zai/glm-4.7"); + }); + + it("models list plain outputs canonical zai key", async () => { + loadConfig.mockReturnValue({ agent: { model: "z.ai/glm-4.7" } }); + const runtime = makeRuntime(); + + const model = { + provider: "zai", + id: "glm-4.7", + name: "GLM-4.7", + input: ["text"], + baseUrl: "https://api.z.ai/v1", + contextWindow: 128000, + }; + + discoverModels.mockReturnValue({ + getAll: () => [model], + getAvailable: () => [model], + }); + + const { modelsListCommand } = await import("./models/list.js"); + await modelsListCommand({ plain: true }, runtime); + + expect(runtime.log).toHaveBeenCalledTimes(1); + expect(runtime.log.mock.calls[0]?.[0]).toBe("zai/glm-4.7"); + }); + + it("models list provider filter normalizes z.ai alias", async () => { + loadConfig.mockReturnValue({ agent: { model: "z.ai/glm-4.7" } }); + const runtime = makeRuntime(); + + const models = [ + { + provider: "zai", + id: "glm-4.7", + name: "GLM-4.7", + input: ["text"], + baseUrl: "https://api.z.ai/v1", + contextWindow: 128000, + }, + { + provider: "openai", + id: "gpt-4.1-mini", + name: "GPT-4.1 mini", + input: ["text"], + baseUrl: "https://api.openai.com/v1", + contextWindow: 128000, + }, + ]; + + discoverModels.mockReturnValue({ + getAll: () => models, + getAvailable: () => models, + }); + + const { modelsListCommand } = await import("./models/list.js"); + await modelsListCommand({ all: true, provider: "z.ai", json: true }, runtime); + + expect(runtime.log).toHaveBeenCalledTimes(1); + const payload = JSON.parse(String(runtime.log.mock.calls[0]?.[0])); + expect(payload.count).toBe(1); + expect(payload.models[0]?.key).toBe("zai/glm-4.7"); + }); +}); diff --git a/src/commands/models.set.test.ts b/src/commands/models.set.test.ts new file mode 100644 index 000000000..03b85d9e6 --- /dev/null +++ b/src/commands/models.set.test.ts @@ -0,0 +1,103 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const readConfigFileSnapshot = vi.fn(); +const writeConfigFile = vi.fn().mockResolvedValue(undefined); +const loadConfig = vi.fn().mockReturnValue({}); + +vi.mock("../config/config.js", () => ({ + CONFIG_PATH_CLAWDBOT: "/tmp/clawdbot.json", + readConfigFileSnapshot, + writeConfigFile, + loadConfig, +})); + +describe("models set + fallbacks", () => { + beforeEach(() => { + readConfigFileSnapshot.mockReset(); + writeConfigFile.mockClear(); + }); + + it("normalizes z.ai provider in models set", async () => { + readConfigFileSnapshot.mockResolvedValue({ + path: "/tmp/clawdbot.json", + exists: true, + raw: "{}", + parsed: {}, + valid: true, + config: {}, + issues: [], + legacyIssues: [], + }); + + const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() }; + const { modelsSetCommand } = await import("./models/set.js"); + + await modelsSetCommand("z.ai/glm-4.7", runtime); + + expect(writeConfigFile).toHaveBeenCalledTimes(1); + const written = writeConfigFile.mock.calls[0]?.[0] as Record< + string, + unknown + >; + expect(written.agent).toEqual({ + model: { primary: "zai/glm-4.7" }, + models: { "zai/glm-4.7": {} }, + }); + }); + + it("normalizes z-ai provider in models fallbacks add", async () => { + readConfigFileSnapshot.mockResolvedValue({ + path: "/tmp/clawdbot.json", + exists: true, + raw: "{}", + parsed: {}, + valid: true, + config: { agent: { model: { fallbacks: [] } } }, + issues: [], + legacyIssues: [], + }); + + const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() }; + const { modelsFallbacksAddCommand } = await import("./models/fallbacks.js"); + + await modelsFallbacksAddCommand("z-ai/glm-4.7", runtime); + + expect(writeConfigFile).toHaveBeenCalledTimes(1); + const written = writeConfigFile.mock.calls[0]?.[0] as Record< + string, + unknown + >; + expect(written.agent).toEqual({ + model: { fallbacks: ["zai/glm-4.7"] }, + models: { "zai/glm-4.7": {} }, + }); + }); + + it("normalizes provider casing in models set", async () => { + readConfigFileSnapshot.mockResolvedValue({ + path: "/tmp/clawdbot.json", + exists: true, + raw: "{}", + parsed: {}, + valid: true, + config: {}, + issues: [], + legacyIssues: [], + }); + + const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() }; + const { modelsSetCommand } = await import("./models/set.js"); + + await modelsSetCommand("Z.AI/glm-4.7", runtime); + + expect(writeConfigFile).toHaveBeenCalledTimes(1); + const written = writeConfigFile.mock.calls[0]?.[0] as Record< + string, + unknown + >; + expect(written.agent).toEqual({ + model: { primary: "zai/glm-4.7" }, + models: { "zai/glm-4.7": {} }, + }); + }); +});