From ca68d57dc22e4475f4e494643e6b71bd57800ce1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 4 Apr 2026 00:04:01 +0900 Subject: [PATCH] test(config): cover legacy heartbeat and memorySearch doctor paths --- src/commands/doctor-config-flow.test.ts | 66 +++++++++++++++++++++++++ src/config/config-misc.test.ts | 58 +++++++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/src/commands/doctor-config-flow.test.ts b/src/commands/doctor-config-flow.test.ts index 4b047be156d..19184256fb3 100644 --- a/src/commands/doctor-config-flow.test.ts +++ b/src/commands/doctor-config-flow.test.ts @@ -1243,6 +1243,72 @@ describe("doctor config flow", () => { } }); + it("warns clearly about legacy heartbeat visibility config and points to doctor --fix", async () => { + const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {}); + try { + await runDoctorConfigWithInput({ + config: { + heartbeat: { + showOk: true, + showAlerts: false, + }, + }, + run: loadAndMaybeMigrateDoctorConfig, + }); + + expect( + noteSpy.mock.calls.some( + ([message, title]) => + title === "Legacy config keys detected" && + String(message).includes("heartbeat:") && + String(message).includes("channels.defaults.heartbeat"), + ), + ).toBe(true); + expect( + noteSpy.mock.calls.some( + ([message, title]) => + title === "Doctor" && + String(message).includes('Run "openclaw doctor --fix" to migrate legacy config keys.'), + ), + ).toBe(true); + } finally { + noteSpy.mockRestore(); + } + }); + + it("warns clearly about legacy memorySearch config and points to doctor --fix", async () => { + const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {}); + try { + await runDoctorConfigWithInput({ + config: { + memorySearch: { + provider: "local", + fallback: "none", + }, + }, + run: loadAndMaybeMigrateDoctorConfig, + }); + + expect( + noteSpy.mock.calls.some( + ([message, title]) => + title === "Legacy config keys detected" && + String(message).includes("memorySearch:") && + String(message).includes("agents.defaults.memorySearch"), + ), + ).toBe(true); + expect( + noteSpy.mock.calls.some( + ([message, title]) => + title === "Doctor" && + String(message).includes('Run "openclaw doctor --fix" to migrate legacy config keys.'), + ), + ).toBe(true); + } finally { + noteSpy.mockRestore(); + } + }); + it("warns clearly about legacy talk config and points to doctor --fix", async () => { const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {}); try { diff --git a/src/config/config-misc.test.ts b/src/config/config-misc.test.ts index 647d93f09bf..c264ced2cff 100644 --- a/src/config/config-misc.test.ts +++ b/src/config/config-misc.test.ts @@ -483,16 +483,70 @@ describe("config strict validation", () => { } }); - it("rejects removed legacy config entries without auto-migrating", async () => { + it("accepts top-level memorySearch via auto-migration and reports legacyIssues", async () => { await withTempHome(async (home) => { await writeOpenClawConfig(home, { - memorySearch: { provider: "local", fallback: "none" }, + memorySearch: { + provider: "local", + fallback: "none", + query: { maxResults: 7 }, + }, }); const snap = await readConfigFileSnapshot(); expect(snap.valid).toBe(true); expect(snap.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true); + expect(snap.sourceConfig.agents?.defaults?.memorySearch).toMatchObject({ + provider: "local", + fallback: "none", + 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 () => { + await withTempHome(async (home) => { + await writeOpenClawConfig(home, { + heartbeat: { + every: "30m", + model: "anthropic/claude-3-5-haiku-20241022", + }, + }); + + const snap = await readConfigFileSnapshot(); + + expect(snap.valid).toBe(true); + expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true); + expect(snap.sourceConfig.agents?.defaults?.heartbeat).toMatchObject({ + every: "30m", + 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 () => { + await withTempHome(async (home) => { + await writeOpenClawConfig(home, { + heartbeat: { + showOk: true, + showAlerts: false, + useIndicator: true, + }, + }); + + const snap = await readConfigFileSnapshot(); + + expect(snap.valid).toBe(true); + expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true); + expect(snap.sourceConfig.channels?.defaults?.heartbeat).toMatchObject({ + showOk: true, + showAlerts: false, + useIndicator: true, + }); + expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined(); }); });