


























@@ -0,0 +1,134 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import type { OpenClawConfig } from "../../config/config.js";
3+import type { RuntimeEnv } from "../../runtime.js";
4+5+const mocks = vi.hoisted(() => ({
6+logConfigUpdated: vi.fn(),
7+readConfigFileSnapshot: vi.fn(),
8+repairCodexRuntimePluginInstallForModelSelection: vi.fn(),
9+replaceConfigFile: vi.fn(),
10+}));
11+12+vi.mock("../../config/config.js", () => ({
13+readConfigFileSnapshot: (...args: unknown[]) => mocks.readConfigFileSnapshot(...args),
14+replaceConfigFile: (...args: unknown[]) => mocks.replaceConfigFile(...args),
15+}));
16+17+vi.mock("../../config/logging.js", () => ({
18+logConfigUpdated: (...args: unknown[]) => mocks.logConfigUpdated(...args),
19+}));
20+21+vi.mock("../codex-runtime-plugin-install.js", () => ({
22+repairCodexRuntimePluginInstallForModelSelection: (...args: unknown[]) =>
23+mocks.repairCodexRuntimePluginInstallForModelSelection(...args),
24+}));
25+26+import { modelsSetCommand } from "./set.js";
27+28+function makeRuntime(): RuntimeEnv {
29+return {
30+log: vi.fn(),
31+error: vi.fn(),
32+exit: vi.fn(),
33+} as unknown as RuntimeEnv;
34+}
35+36+describe("modelsSetCommand", () => {
37+beforeEach(() => {
38+vi.clearAllMocks();
39+mocks.replaceConfigFile.mockResolvedValue(undefined);
40+mocks.repairCodexRuntimePluginInstallForModelSelection.mockResolvedValue({ warnings: [] });
41+});
42+43+it("resolves aliases from runtime config while writing only source config", async () => {
44+const sourceConfig = {
45+agents: {
46+defaults: {
47+models: {
48+"anthropic/claude-sonnet-4-6": {},
49+},
50+},
51+},
52+} as unknown as OpenClawConfig;
53+const runtimeConfig = {
54+agents: {
55+defaults: {
56+models: {
57+"anthropic/claude-sonnet-4-6": { alias: "sonnet" },
58+},
59+},
60+},
61+} as unknown as OpenClawConfig;
62+mocks.readConfigFileSnapshot.mockResolvedValue({
63+valid: true,
64+hash: "config-hash",
65+ sourceConfig,
66+ runtimeConfig,
67+config: runtimeConfig,
68+});
69+const runtime = makeRuntime();
70+71+await modelsSetCommand("sonnet", runtime);
72+73+expect(mocks.replaceConfigFile).toHaveBeenCalledOnce();
74+const [replaceParams] = mocks.replaceConfigFile.mock.calls[0] ?? [];
75+expect(replaceParams?.nextConfig.agents?.defaults?.model).toEqual({
76+primary: "anthropic/claude-sonnet-4-6",
77+});
78+expect(replaceParams?.nextConfig.agents?.defaults?.models).toEqual({
79+"anthropic/claude-sonnet-4-6": {},
80+});
81+expect(replaceParams?.nextConfig.agents?.defaults?.models).not.toHaveProperty("openai/sonnet");
82+expect(mocks.repairCodexRuntimePluginInstallForModelSelection).toHaveBeenCalledWith({
83+cfg: replaceParams?.nextConfig,
84+model: "anthropic/claude-sonnet-4-6",
85+});
86+expect(runtime.log).toHaveBeenCalledWith("Default model: anthropic/claude-sonnet-4-6");
87+});
88+89+it("keeps authored aliases ahead of runtime-only aliases", async () => {
90+const sourceConfig = {
91+agents: {
92+defaults: {
93+models: {
94+"openai/gpt-5.5": { alias: "sonnet" },
95+},
96+},
97+},
98+} as unknown as OpenClawConfig;
99+const runtimeConfig = {
100+agents: {
101+defaults: {
102+models: {
103+"openai/gpt-5.5": { alias: "sonnet" },
104+"anthropic/claude-sonnet-4-6": { alias: "sonnet" },
105+},
106+},
107+},
108+} as unknown as OpenClawConfig;
109+mocks.readConfigFileSnapshot.mockResolvedValue({
110+valid: true,
111+hash: "config-hash",
112+ sourceConfig,
113+ runtimeConfig,
114+config: runtimeConfig,
115+});
116+const runtime = makeRuntime();
117+118+await modelsSetCommand("sonnet", runtime);
119+120+expect(mocks.replaceConfigFile).toHaveBeenCalledOnce();
121+const [replaceParams] = mocks.replaceConfigFile.mock.calls[0] ?? [];
122+expect(replaceParams?.nextConfig.agents?.defaults?.model).toEqual({
123+primary: "openai/gpt-5.5",
124+});
125+expect(replaceParams?.nextConfig.agents?.defaults?.models).toEqual({
126+"openai/gpt-5.5": { alias: "sonnet" },
127+});
128+expect(mocks.repairCodexRuntimePluginInstallForModelSelection).toHaveBeenCalledWith({
129+cfg: replaceParams?.nextConfig,
130+model: "openai/gpt-5.5",
131+});
132+expect(runtime.log).toHaveBeenCalledWith("Default model: openai/gpt-5.5");
133+});
134+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。