mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 12:40:28 +00:00
test: update legacy config doctor expectations
This commit is contained in:
@@ -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", () => {
|
||||||
@@ -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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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".',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
|
|||||||
registerService() {},
|
registerService() {},
|
||||||
registerCliBackend() {},
|
registerCliBackend() {},
|
||||||
registerConfigMigration() {},
|
registerConfigMigration() {},
|
||||||
registerLegacyConfigMigration() {},
|
|
||||||
registerAutoEnableProbe() {},
|
registerAutoEnableProbe() {},
|
||||||
registerProvider() {},
|
registerProvider() {},
|
||||||
registerSpeechProvider() {},
|
registerSpeechProvider() {},
|
||||||
|
|||||||
Reference in New Issue
Block a user