test: update legacy config doctor expectations

This commit is contained in:
Peter Steinberger
2026-04-05 16:12:10 +01:00
parent 97878b853a
commit 37b3acad34
10 changed files with 89 additions and 184 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { applyChannelDoctorCompatibilityMigrations } from "./legacy-config.js"; import { applyChannelDoctorCompatibilityMigrations } from "./channel-legacy-config-migrate.js";
describe("bundled channel legacy config migrations", () => { describe("bundled channel legacy config migrations", () => {
it("normalizes legacy private-network aliases exposed through bundled contract surfaces", () => { it("normalizes legacy private-network aliases exposed through bundled contract surfaces", () => {

View File

@@ -497,7 +497,7 @@ describe("config strict validation", () => {
} }
}); });
it("accepts top-level memorySearch via auto-migration and reports legacyIssues", async () => { it("rejects top-level memorySearch until doctor repairs it and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
memorySearch: { memorySearch: {
@@ -509,18 +509,17 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
expect(snap.sourceConfig.agents?.defaults?.memorySearch).toMatchObject({ expect((snap.sourceConfig as { memorySearch?: unknown }).memorySearch).toMatchObject({
provider: "local", provider: "local",
fallback: "none", fallback: "none",
query: { maxResults: 7 }, query: { maxResults: 7 },
}); });
expect((snap.sourceConfig as { memorySearch?: unknown }).memorySearch).toBeUndefined();
}); });
}); });
it("accepts top-level heartbeat agent settings via auto-migration and reports legacyIssues", async () => { it("rejects top-level heartbeat agent settings until doctor repairs them and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
heartbeat: { heartbeat: {
@@ -531,17 +530,16 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
expect(snap.sourceConfig.agents?.defaults?.heartbeat).toMatchObject({ expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toMatchObject({
every: "30m", every: "30m",
model: "anthropic/claude-3-5-haiku-20241022", model: "anthropic/claude-3-5-haiku-20241022",
}); });
expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined();
}); });
}); });
it("accepts top-level heartbeat visibility via auto-migration and reports legacyIssues", async () => { it("rejects top-level heartbeat visibility until doctor repairs it and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
heartbeat: { heartbeat: {
@@ -553,18 +551,17 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
expect(snap.sourceConfig.channels?.defaults?.heartbeat).toMatchObject({ expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toMatchObject({
showOk: true, showOk: true,
showAlerts: false, showAlerts: false,
useIndicator: true, useIndicator: true,
}); });
expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined();
}); });
}); });
it("accepts legacy messages.tts provider keys via auto-migration and reports legacyIssues", async () => { it("rejects legacy messages.tts provider keys until doctor repairs them and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
messages: { messages: {
@@ -580,15 +577,15 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "messages.tts")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "messages.tts")).toBe(true);
expect(snap.sourceConfig.messages?.tts?.providers?.elevenlabs).toEqual({ expect(snap.sourceConfig.messages?.tts).toEqual({
apiKey: "test-key", provider: "elevenlabs",
voiceId: "voice-1", elevenlabs: {
apiKey: "test-key",
voiceId: "voice-1",
},
}); });
expect(
(snap.sourceConfig.messages?.tts as Record<string, unknown> | undefined)?.elevenlabs,
).toBeUndefined();
}); });
}); });
@@ -614,7 +611,7 @@ describe("config strict validation", () => {
}); });
}); });
it("accepts legacy sandbox perSession via auto-migration and reports legacyIssues", async () => { it("rejects legacy sandbox perSession until doctor repairs it and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
agents: { agents: {
@@ -636,21 +633,17 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "agents.defaults.sandbox")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "agents.defaults.sandbox")).toBe(
true, true,
); );
expect(snap.legacyIssues.some((issue) => issue.path === "agents.list")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "agents.list")).toBe(true);
expect(snap.sourceConfig.agents?.defaults?.sandbox).toEqual({ expect(snap.sourceConfig.agents?.defaults?.sandbox).toEqual({ perSession: true });
scope: "session", expect(snap.sourceConfig.agents?.list?.[0]?.sandbox).toEqual({ perSession: false });
});
expect(snap.sourceConfig.agents?.list?.[0]?.sandbox).toEqual({
scope: "shared",
});
}); });
}); });
it("accepts legacy x_search auth via auto-migration and reports legacyIssues", async () => { it("rejects legacy x_search auth until doctor repairs it and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
tools: { tools: {
@@ -664,20 +657,17 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "tools.web.x_search.apiKey")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "tools.web.x_search.apiKey")).toBe(
true, true,
); );
expect(snap.sourceConfig.plugins?.entries?.xai?.config?.webSearch).toMatchObject({
apiKey: "test-key",
});
expect( expect(
(snap.sourceConfig.tools?.web?.x_search as Record<string, unknown> | undefined)?.apiKey, (snap.sourceConfig.tools?.web?.x_search as Record<string, unknown> | undefined)?.apiKey,
).toBeUndefined(); ).toBe("test-key");
}); });
}); });
it("accepts legacy thread binding ttlHours via auto-migration and reports legacyIssues", async () => { it("rejects legacy thread binding ttlHours until doctor repairs it and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
session: { session: {
@@ -703,37 +693,18 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "session.threadBindings")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "session.threadBindings")).toBe(true);
expect(snap.legacyIssues.some((issue) => issue.path === "channels")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "channels")).toBe(true);
expect(snap.sourceConfig.session?.threadBindings).toMatchObject({ expect(snap.sourceConfig.session?.threadBindings).toMatchObject({ ttlHours: 24 });
idleHours: 24, expect(snap.sourceConfig.channels?.discord?.threadBindings).toMatchObject({ ttlHours: 12 });
});
expect(snap.sourceConfig.channels?.discord?.threadBindings).toMatchObject({
idleHours: 12,
});
expect(snap.sourceConfig.channels?.discord?.accounts?.alpha?.threadBindings).toMatchObject({ expect(snap.sourceConfig.channels?.discord?.accounts?.alpha?.threadBindings).toMatchObject({
idleHours: 6, ttlHours: 6,
}); });
expect(
(snap.sourceConfig.session?.threadBindings as Record<string, unknown> | undefined)
?.ttlHours,
).toBeUndefined();
expect(
(snap.sourceConfig.channels?.discord?.threadBindings as Record<string, unknown> | undefined)
?.ttlHours,
).toBeUndefined();
expect(
(
snap.sourceConfig.channels?.discord?.accounts?.alpha?.threadBindings as
| Record<string, unknown>
| undefined
)?.ttlHours,
).toBeUndefined();
}); });
}); });
it("accepts legacy channel streaming aliases via auto-migration and reports legacyIssues", async () => { it("rejects legacy channel streaming aliases until doctor repairs them and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
channels: { channels: {
@@ -764,7 +735,7 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "channels.telegram")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "channels.telegram")).toBe(true);
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord")).toBe(true);
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
@@ -775,36 +746,20 @@ describe("config strict validation", () => {
true, true,
); );
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true);
expect(snap.sourceConfig.channels?.telegram).toMatchObject({ expect(snap.sourceConfig.channels?.telegram).toMatchObject({ streamMode: "block" });
streaming: "block", expect(snap.sourceConfig.channels?.discord).toMatchObject({ streaming: false });
});
expect(
(snap.sourceConfig.channels?.telegram as Record<string, unknown> | undefined)?.streamMode,
).toBeUndefined();
expect(snap.sourceConfig.channels?.discord).toMatchObject({
streaming: "off",
});
expect(snap.sourceConfig.channels?.discord?.accounts?.work).toMatchObject({ expect(snap.sourceConfig.channels?.discord?.accounts?.work).toMatchObject({
streaming: "block", streamMode: "block",
}); });
expect( expect(snap.sourceConfig.channels?.googlechat).toMatchObject({ streamMode: "append" });
(snap.sourceConfig.channels?.googlechat as Record<string, unknown> | undefined)?.streamMode, expect(snap.sourceConfig.channels?.googlechat?.accounts?.work).toMatchObject({
).toBeUndefined(); streamMode: "replace",
expect(
(
snap.sourceConfig.channels?.googlechat?.accounts?.work as
| Record<string, unknown>
| undefined
)?.streamMode,
).toBeUndefined();
expect(snap.sourceConfig.channels?.slack).toMatchObject({
streaming: "partial",
nativeStreaming: true,
}); });
expect(snap.sourceConfig.channels?.slack).toMatchObject({ streaming: true });
}); });
}); });
it("accepts legacy nested channel allow aliases via auto-migration and reports legacyIssues", async () => { it("rejects legacy nested channel allow aliases until doctor repairs them and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
channels: { channels: {
@@ -869,7 +824,7 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true);
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack.accounts")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack.accounts")).toBe(
true, true,
@@ -882,39 +837,17 @@ describe("config strict validation", () => {
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
true, true,
); );
expect(snap.sourceConfig.channels?.slack?.channels?.ops).toMatchObject({ expect(snap.sourceConfig.channels?.slack?.channels?.ops).toMatchObject({ allow: false });
enabled: false,
});
expect(snap.sourceConfig.channels?.googlechat?.groups?.["spaces/aaa"]).toMatchObject({ expect(snap.sourceConfig.channels?.googlechat?.groups?.["spaces/aaa"]).toMatchObject({
enabled: false, allow: false,
}); });
expect(snap.sourceConfig.channels?.discord?.guilds?.["100"]?.channels?.general).toMatchObject( expect(snap.sourceConfig.channels?.discord?.guilds?.["100"]?.channels?.general).toMatchObject(
{ { allow: false },
enabled: false,
},
); );
expect(
(snap.sourceConfig.channels?.slack?.channels?.ops as Record<string, unknown> | undefined)
?.allow,
).toBeUndefined();
expect(
(
snap.sourceConfig.channels?.googlechat?.groups?.["spaces/aaa"] as
| Record<string, unknown>
| undefined
)?.allow,
).toBeUndefined();
expect(
(
snap.sourceConfig.channels?.discord?.guilds?.["100"]?.channels?.general as
| Record<string, unknown>
| undefined
)?.allow,
).toBeUndefined();
}); });
}); });
it("accepts telegram groupMentionsOnly via auto-migration and reports legacyIssues", async () => { it("rejects telegram groupMentionsOnly until doctor repairs it and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
channels: { channels: {
@@ -926,21 +859,15 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect( expect(
snap.legacyIssues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly"), snap.legacyIssues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly"),
).toBe(true); ).toBe(true);
expect(snap.sourceConfig.channels?.telegram?.groups?.["*"]).toMatchObject({ expect(snap.sourceConfig.channels?.telegram).toMatchObject({ groupMentionsOnly: true });
requireMention: true,
});
expect(
(snap.sourceConfig.channels?.telegram as Record<string, unknown> | undefined)
?.groupMentionsOnly,
).toBeUndefined();
}); });
}); });
it("accepts legacy plugins.entries.*.config.tts provider keys via auto-migration", async () => { it("rejects legacy plugins.entries.*.config.tts provider keys until doctor repairs them", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
plugins: { plugins: {
@@ -962,7 +889,7 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "plugins.entries")).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "plugins.entries")).toBe(true);
const voiceCallTts = ( const voiceCallTts = (
snap.sourceConfig.plugins?.entries as snap.sourceConfig.plugins?.entries as
@@ -979,15 +906,17 @@ describe("config strict validation", () => {
> >
| undefined | undefined
)?.["voice-call"]?.config?.tts; )?.["voice-call"]?.config?.tts;
expect(voiceCallTts?.providers?.openai).toEqual({ expect(voiceCallTts).toEqual({
model: "gpt-4o-mini-tts", provider: "openai",
voice: "alloy", openai: {
model: "gpt-4o-mini-tts",
voice: "alloy",
},
}); });
expect(voiceCallTts?.openai).toBeUndefined();
}); });
}); });
it("accepts legacy discord voice tts provider keys via auto-migration and reports legacyIssues", async () => { it("rejects legacy discord voice tts provider keys until doctor repairs them and reports legacyIssues", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
await writeOpenClawConfig(home, { await writeOpenClawConfig(home, {
channels: { channels: {
@@ -1017,32 +946,24 @@ describe("config strict validation", () => {
const snap = await readConfigFileSnapshot(); const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true); expect(snap.valid).toBe(false);
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.voice.tts")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.voice.tts")).toBe(
true, true,
); );
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe( expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
true, true,
); );
expect(snap.sourceConfig.channels?.discord?.voice?.tts?.providers?.elevenlabs).toEqual({ expect(snap.sourceConfig.channels?.discord?.voice?.tts).toEqual({
voiceId: "voice-1", provider: "elevenlabs",
elevenlabs: {
voiceId: "voice-1",
},
}); });
expect( expect(snap.sourceConfig.channels?.discord?.accounts?.main?.voice?.tts).toEqual({
snap.sourceConfig.channels?.discord?.accounts?.main?.voice?.tts?.providers?.microsoft, edge: {
).toEqual({ voice: "en-US-AvaNeural",
voice: "en-US-AvaNeural", },
}); });
expect(
(snap.sourceConfig.channels?.discord?.voice?.tts as Record<string, unknown> | undefined)
?.elevenlabs,
).toBeUndefined();
expect(
(
snap.sourceConfig.channels?.discord?.accounts?.main?.voice?.tts as
| Record<string, unknown>
| undefined
)?.edge,
).toBeUndefined();
}); });
}); });

View File

@@ -2,10 +2,15 @@ import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
const { loadConfig, migrateLegacyConfig, readConfigFileSnapshot, validateConfigObject } = const { loadConfig, readConfigFileSnapshot, validateConfigObject } =
await vi.importActual<typeof import("./config.js")>("./config.js"); await vi.importActual<typeof import("./config.js")>("./config.js");
const { migrateLegacyConfig: migrateLegacyConfigFromDoctor } = await vi.importActual<
typeof import("../commands/doctor/shared/legacy-config-migrate.js")
>("../commands/doctor/shared/legacy-config-migrate.js");
import { withTempHome } from "./test-helpers.js"; import { withTempHome } from "./test-helpers.js";
const migrateLegacyConfig = migrateLegacyConfigFromDoctor;
async function expectLoadRejectionPreservesField(params: { async function expectLoadRejectionPreservesField(params: {
config: unknown; config: unknown;
readValue: (parsed: unknown) => unknown; readValue: (parsed: unknown) => unknown;
@@ -219,7 +224,7 @@ describe("legacy config detection", () => {
await withSnapshotForConfig( await withSnapshotForConfig(
{ channels: { telegram: { groupMentionsOnly: true } } }, { channels: { telegram: { groupMentionsOnly: true } } },
async (ctx) => { async (ctx) => {
expect(ctx.snapshot.valid).toBe(true); expect(ctx.snapshot.valid).toBe(false);
expect( expect(
ctx.snapshot.legacyIssues.some( ctx.snapshot.legacyIssues.some(
(issue) => issue.path === "channels.telegram.groupMentionsOnly", (issue) => issue.path === "channels.telegram.groupMentionsOnly",
@@ -263,7 +268,7 @@ describe("legacy config detection", () => {
await withSnapshotForConfig( await withSnapshotForConfig(
{ memorySearch: { provider: "local", fallback: "none" } }, { memorySearch: { provider: "local", fallback: "none" } },
async (ctx) => { async (ctx) => {
expect(ctx.snapshot.valid).toBe(true); expect(ctx.snapshot.valid).toBe(false);
expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true); expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
}, },
); );
@@ -272,7 +277,7 @@ describe("legacy config detection", () => {
await withSnapshotForConfig( await withSnapshotForConfig(
{ heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" } }, { heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" } },
async (ctx) => { async (ctx) => {
expect(ctx.snapshot.valid).toBe(true); expect(ctx.snapshot.valid).toBe(false);
expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true); expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
}, },
); );
@@ -316,7 +321,7 @@ describe("legacy config detection", () => {
await withSnapshotForConfig( await withSnapshotForConfig(
{ memorySearch: { provider: "local", fallback: "none" } }, { memorySearch: { provider: "local", fallback: "none" } },
async (ctx) => { async (ctx) => {
expect(ctx.snapshot.valid).toBe(true); expect(ctx.snapshot.valid).toBe(false);
expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true); expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
}, },
); );

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { migrateLegacyConfig } from "./legacy-migrate.js"; import { migrateLegacyConfig } from "../commands/doctor/shared/legacy-config-migrate.js";
import type { OpenClawConfig } from "./types.js"; import type { OpenClawConfig } from "./types.js";
import { validateConfigObject } from "./validation.js"; import { validateConfigObject } from "./validation.js";

View File

@@ -389,7 +389,7 @@ describe("web search provider config", () => {
expect(res.ok).toBe(false); expect(res.ok).toBe(false);
}); });
it("accepts legacy scoped provider config for bundled providers via auto-migration", () => { it("rejects legacy scoped provider config for bundled providers until doctor repairs it", () => {
const res = validateConfigObjectWithPlugins({ const res = validateConfigObjectWithPlugins({
tools: { tools: {
web: { web: {
@@ -403,21 +403,7 @@ describe("web search provider config", () => {
}, },
}); });
expect(res.ok).toBe(true); expect(res.ok).toBe(false);
if (!res.ok) {
return;
}
expect(res.config.tools?.web?.search).toEqual({
provider: "gemini",
});
expect(res.config.plugins?.entries?.google).toEqual({
enabled: true,
config: {
webSearch: {
apiKey: "legacy-key",
},
},
});
}); });
it("accepts gemini provider with no extra config", () => { it("accepts gemini provider with no extra config", () => {

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { migrateLegacyConfig } from "./legacy-migrate.js"; import { migrateLegacyConfig } from "../commands/doctor/shared/legacy-config-migrate.js";
import { import {
validateConfigObjectRawWithPlugins, validateConfigObjectRawWithPlugins,
validateConfigObjectWithPlugins, validateConfigObjectWithPlugins,

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { migrateLegacyConfig } from "../commands/doctor/shared/legacy-config-migrate.js";
import type { OpenClawConfig } from "./config.js"; import type { OpenClawConfig } from "./config.js";
import { migrateLegacyConfig } from "./legacy-migrate.js";
import { import {
listLegacyWebSearchConfigPaths, listLegacyWebSearchConfigPaths,
migrateLegacyWebSearchConfig, migrateLegacyWebSearchConfig,
@@ -108,7 +108,7 @@ describe("legacy web search config", () => {
{ {
path: "tools.web.search", path: "tools.web.search",
message: message:
"tools.web.search provider-owned config moved to plugins.entries.<plugin>.config.webSearch (auto-migrated on load).", 'tools.web.search provider-owned config moved to plugins.entries.<plugin>.config.webSearch. Run "openclaw doctor --fix".',
}, },
]); ]);

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { migrateLegacyConfig } from "./legacy-migrate.js"; import { migrateLegacyConfig } from "../commands/doctor/shared/legacy-config-migrate.js";
import { validateConfigObjectRaw } from "./validation.js"; import { validateConfigObjectRaw } from "./validation.js";
describe("thread binding config keys", () => { describe("thread binding config keys", () => {

View File

@@ -3117,7 +3117,7 @@ describe("secrets runtime snapshot", () => {
} }
}); });
it("migrates legacy x_search SecretRefs into the xai plugin webSearch auth at runtime", async () => { it("keeps legacy x_search SecretRefs in place until doctor repairs them", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({ const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({ config: asConfig({
tools: { tools: {
@@ -3138,17 +3138,14 @@ describe("secrets runtime snapshot", () => {
}); });
expect((snapshot.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({ expect((snapshot.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
apiKey: "xai-runtime-key",
enabled: true, enabled: true,
model: "grok-4-1-fast", model: "grok-4-1-fast",
}); });
expect(snapshot.config.plugins?.entries?.xai?.config).toEqual({ expect(snapshot.config.plugins?.entries?.xai).toBeUndefined();
webSearch: {
apiKey: "xai-runtime-key",
},
});
}); });
it("still migrates legacy x_search auth when general legacy migration returns an invalid config", async () => { it("still resolves legacy x_search auth in place even when unrelated legacy config is present", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({ const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({ config: asConfig({
tools: { tools: {
@@ -3174,13 +3171,10 @@ describe("secrets runtime snapshot", () => {
}); });
expect((snapshot.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({ expect((snapshot.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
apiKey: "xai-runtime-key-invalid-config",
enabled: true, enabled: true,
}); });
expect(snapshot.config.plugins?.entries?.xai?.config).toEqual({ expect(snapshot.config.plugins?.entries?.xai).toBeUndefined();
webSearch: {
apiKey: "xai-runtime-key-invalid-config",
},
});
}); });
it("does not force-enable xai at runtime for knob-only x_search config", async () => { it("does not force-enable xai at runtime for knob-only x_search config", async () => {

View File

@@ -19,7 +19,6 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
registerService() {}, registerService() {},
registerCliBackend() {}, registerCliBackend() {},
registerConfigMigration() {}, registerConfigMigration() {},
registerLegacyConfigMigration() {},
registerAutoEnableProbe() {}, registerAutoEnableProbe() {},
registerProvider() {}, registerProvider() {},
registerSpeechProvider() {}, registerSpeechProvider() {},