Files
openclaw/src/plugins/provider-auth-choice-helpers.test.ts
Sathvik-1007 8d57d745cf fix: wizard no clobber model.primary on re-run
two bugs. both squash user model choice silently.

bug 1: applyDefaultModel() unconditional primary: model overwrite.
wizard calls with setDefaultModel=true, provider returns its default
(e.g. openrouter/auto), bam user primary gone. fix: existingPrimary ?? model.

bug 2: applyModelFallbacksFromSelection() phantom primary injection.
when no primary configured, resolvedKey (hardcoded default) written as
primary via nullish coalescing fallback. fix: conditional spread — only
include primary key when one actually existed.

tests for both. closes #70696
2026-04-24 19:55:20 +01:00

176 lines
5.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { applyDefaultModel, applyProviderAuthConfigPatch } from "./provider-auth-choice-helpers.js";
describe("applyProviderAuthConfigPatch", () => {
const base = {
agents: {
defaults: {
model: { primary: "anthropic/claude-sonnet-4-6", fallbacks: ["openai/gpt-5.2"] },
models: {
"anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
"anthropic/claude-opus-4-6": { alias: "Opus" },
"openai/gpt-5.2": {},
},
},
},
};
it("merges default model maps by default so other providers survive login", () => {
const patch = { agents: { defaults: { models: { "openai/gpt-5.5": {} } } } };
const next = applyProviderAuthConfigPatch(base, patch);
expect(next.agents?.defaults?.models).toEqual({
...base.agents.defaults.models,
"openai/gpt-5.5": {},
});
expect(next.agents?.defaults?.model).toEqual(base.agents.defaults.model);
});
it("replaces the allowlist only when replaceDefaultModels is set", () => {
const patch = {
agents: {
defaults: {
models: {
"claude-cli/claude-sonnet-4-6": { alias: "Sonnet" },
"openai/gpt-5.2": {},
},
},
},
};
const next = applyProviderAuthConfigPatch(base, patch, { replaceDefaultModels: true });
expect(next.agents?.defaults?.models).toEqual(patch.agents.defaults.models);
expect(next.agents?.defaults?.model).toEqual(base.agents.defaults.model);
});
it("drops prototype-pollution keys from the merge", () => {
const patch = JSON.parse('{"__proto__":{"polluted":true},"agents":{"defaults":{}}}');
const next = applyProviderAuthConfigPatch(base, patch);
expect(next.agents?.defaults?.models).toEqual(base.agents.defaults.models);
expect(({} as Record<string, unknown>).polluted).toBeUndefined();
expect(Object.getPrototypeOf(next).polluted).toBeUndefined();
});
it("drops prototype-pollution keys from opt-in model replacement", () => {
const patch = JSON.parse(
'{"agents":{"defaults":{"models":{"__proto__":{"polluted":true},"claude-cli/claude-sonnet-4-6":{"alias":"Sonnet","params":{"constructor":{"polluted":true},"maxTokens":12000}}}}}}',
);
const next = applyProviderAuthConfigPatch(base, patch, { replaceDefaultModels: true });
const models = next.agents?.defaults?.models;
expect(models).toEqual({
"claude-cli/claude-sonnet-4-6": {
alias: "Sonnet",
params: { maxTokens: 12000 },
},
});
expect(Object.prototype.hasOwnProperty.call(models, "__proto__")).toBe(false);
expect(Object.getPrototypeOf(Object.assign({}, models)).polluted).toBeUndefined();
expect(({} as Record<string, unknown>).polluted).toBeUndefined();
});
it("keeps normal recursive merges for unrelated provider auth patch fields", () => {
const base = {
agents: {
defaults: {
contextPruning: {
mode: "cache-ttl",
ttl: "30m",
},
},
},
} satisfies OpenClawConfig;
const patch = {
agents: {
defaults: {
contextPruning: {
ttl: "1h",
},
},
},
};
const next = applyProviderAuthConfigPatch(base, patch);
expect(next).toEqual({
agents: {
defaults: {
contextPruning: {
mode: "cache-ttl",
ttl: "1h",
},
},
},
});
});
});
describe("applyDefaultModel", () => {
it("sets the primary when none exists", () => {
const config = {
agents: { defaults: {} },
} as OpenClawConfig;
const next = applyDefaultModel(config, "openrouter/auto");
expect(next.agents?.defaults?.model).toEqual({ primary: "openrouter/auto" });
});
it("overwrites an existing primary by default", () => {
const config = {
agents: {
defaults: {
model: { primary: "anthropic/claude-opus-4-6" },
},
},
} as OpenClawConfig;
const next = applyDefaultModel(config, "openrouter/auto");
expect(next.agents?.defaults?.model).toEqual({
primary: "openrouter/auto",
});
});
it("preserves an existing primary when requested", () => {
const config = {
agents: {
defaults: {
model: { primary: "anthropic/claude-opus-4-6" },
},
},
} as OpenClawConfig;
const next = applyDefaultModel(config, "openrouter/auto", {
preserveExistingPrimary: true,
});
expect(next.agents?.defaults?.model).toEqual({
primary: "anthropic/claude-opus-4-6",
});
});
it("preserves an existing primary and keeps fallbacks", () => {
const config = {
agents: {
defaults: {
model: {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4"],
},
},
},
} as OpenClawConfig;
const next = applyDefaultModel(config, "openrouter/auto", {
preserveExistingPrimary: true,
});
expect(next.agents?.defaults?.model).toEqual({
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4"],
});
});
it("adds the model to the allowlist", () => {
const config = {
agents: { defaults: { models: { "anthropic/claude-sonnet-4-6": {} } } },
} as OpenClawConfig;
const next = applyDefaultModel(config, "openrouter/auto");
expect(next.agents?.defaults?.models).toEqual({
"anthropic/claude-sonnet-4-6": {},
"openrouter/auto": {},
});
});
});