diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 9e55ae41ec47..81eada906bd9 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -380,6 +380,23 @@ describe("update-cli", () => { return calls[index]; }; + const syncPluginCall = (index = 0) => { + const calls = syncPluginsForUpdateChannel.mock.calls as unknown as Array< + [{ channel?: string; config?: OpenClawConfig }] + >; + return calls[index]?.[0]; + }; + + const npmPluginUpdateCall = (index = 0) => { + const calls = updateNpmInstalledPlugins.mock.calls as unknown as Array< + [{ timeoutMs?: number }] + >; + return calls[index]?.[0]; + }; + + const pluginWarning = (result?: UpdateRunResult) => result?.postUpdate?.plugins?.warnings?.[0]; + const pluginOutcome = (result?: UpdateRunResult) => result?.postUpdate?.plugins?.npm.outcomes[0]; + const expectPackageInstallSpec = (spec: string) => { expect(runGatewayUpdate).not.toHaveBeenCalled(); const call = ( @@ -799,18 +816,13 @@ describe("update-cli", () => { await updateCommand({ channel: "dev", yes: true, restart: false }); - expect(spawn).toHaveBeenCalledWith( - expect.stringMatching(/node/), - [entrypoints[0], "update", "--no-restart", "--yes"], - expect.objectContaining({ - stdio: "inherit", - env: expect.objectContaining({ - OPENCLAW_UPDATE_POST_CORE: "1", - OPENCLAW_UPDATE_POST_CORE_CHANNEL: "dev", - OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL: "dev", - }), - }), - ); + const call = spawnCall(); + expect(call?.[0]).toMatch(/node/); + expect(call?.[1]).toEqual([entrypoints[0], "update", "--no-restart", "--yes"]); + expect(call?.[2]?.stdio).toBe("inherit"); + expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE).toBe("1"); + expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE_CHANNEL).toBe("dev"); + expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL).toBe("dev"); expect(replaceConfigFile).not.toHaveBeenCalled(); expect(syncPluginsForUpdateChannel).not.toHaveBeenCalled(); expect(updateNpmInstalledPlugins).not.toHaveBeenCalled(); @@ -891,10 +903,10 @@ describe("update-cli", () => { ); expect(runGatewayUpdate).not.toHaveBeenCalled(); - expect(runCommandWithTimeout).not.toHaveBeenCalledWith( - ["npm", "i", "-g", expect.any(String)], - expect.anything(), - ); + const installCall = ( + vi.mocked(runCommandWithTimeout).mock.calls as unknown as Array<[string[], unknown]> + ).find(([argv]) => argv[0] === "npm" && argv[1] === "i" && argv[2] === "-g"); + expect(installCall).toBeUndefined(); expect(defaultRuntime.exit).toHaveBeenCalledWith(0); expect(syncPluginsForUpdateChannel).toHaveBeenCalledTimes(1); expect(updateNpmInstalledPlugins).toHaveBeenCalledTimes(1); @@ -999,14 +1011,8 @@ describe("update-cli", () => { }, baseHash: "stable-hash", }); - expect(syncPluginsForUpdateChannel).toHaveBeenCalledWith( - expect.objectContaining({ - channel: "dev", - config: expect.objectContaining({ - update: expect.objectContaining({ channel: "dev" }), - }), - }), - ); + expect(syncPluginCall()?.channel).toBe("dev"); + expect(syncPluginCall()?.config?.update?.channel).toBe("dev"); }); it("post-core resume mode retries update channel persistence after config hash drift", async () => { @@ -1071,14 +1077,8 @@ describe("update-cli", () => { }, baseHash: "newer-hash", }); - expect(syncPluginsForUpdateChannel).toHaveBeenCalledWith( - expect.objectContaining({ - config: expect.objectContaining({ - meta: expect.objectContaining({ lastTouchedVersion: "2026.4.30" }), - update: expect.objectContaining({ channel: "dev" }), - }), - }), - ); + expect(syncPluginCall()?.config?.meta?.lastTouchedVersion).toBe("2026.4.30"); + expect(syncPluginCall()?.config?.update?.channel).toBe("dev"); }); it("passes the update timeout budget into post-core plugin updates", async () => { @@ -1092,9 +1092,7 @@ describe("update-cli", () => { }, ); - expect(updateNpmInstalledPlugins).toHaveBeenCalledWith( - expect.objectContaining({ timeoutMs: 1_800_000 }), - ); + expect(npmPluginUpdateCall()?.timeoutMs).toBe(1_800_000); }); it("uses a fail-closed integrity policy for post-core plugin updates", async () => { @@ -1200,16 +1198,12 @@ describe("update-cli", () => { }, ]); expect(jsonOutput?.postUpdate?.plugins?.status).toBe("warning"); - expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]).toMatchObject({ - pluginId: "demo", - guidance: [ - "Run openclaw doctor --fix to attempt automatic repair.", - "Run openclaw plugins inspect demo --runtime --json for details.", - ], - }); - expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]?.reason).toContain( - "npm package integrity drift", - ); + expect(pluginWarning(jsonOutput)?.pluginId).toBe("demo"); + expect(pluginWarning(jsonOutput)?.guidance).toEqual([ + "Run openclaw doctor --fix to attempt automatic repair.", + "Run openclaw plugins inspect demo --runtime --json for details.", + ]); + expect(pluginWarning(jsonOutput)?.reason).toContain("npm package integrity drift"); expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]?.status).toBe("error"); expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]?.message).toContain( "Run openclaw doctor --fix to attempt automatic repair.", @@ -1268,16 +1262,10 @@ describe("update-cli", () => { | undefined; expect(jsonOutput?.status).toBe("ok"); expect(jsonOutput?.postUpdate?.plugins?.status).toBe("warning"); - expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]).toMatchObject({ - pluginId: "demo", - }); - expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]?.reason).toContain( - "package.json is missing", - ); - expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]).toMatchObject({ - pluginId: "demo", - status: "error", - }); + expect(pluginWarning(jsonOutput)?.pluginId).toBe("demo"); + expect(pluginWarning(jsonOutput)?.reason).toContain("package.json is missing"); + expect(pluginOutcome(jsonOutput)?.pluginId).toBe("demo"); + expect(pluginOutcome(jsonOutput)?.status).toBe("error"); }); it("prints non-fatal plugin warnings in human update output", async () => { @@ -1335,17 +1323,13 @@ describe("update-cli", () => { | UpdateRunResult | undefined; expect(jsonOutput?.postUpdate?.plugins?.status).toBe("warning"); - expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]).toMatchObject({ - pluginId: "demo", - guidance: [ - "Run openclaw doctor --fix to attempt automatic repair.", - "Run openclaw plugins inspect demo --runtime --json for details.", - ], - }); - expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]).toMatchObject({ - pluginId: "demo", - status: "skipped", - }); + expect(pluginWarning(jsonOutput)?.pluginId).toBe("demo"); + expect(pluginWarning(jsonOutput)?.guidance).toEqual([ + "Run openclaw doctor --fix to attempt automatic repair.", + "Run openclaw plugins inspect demo --runtime --json for details.", + ]); + expect(pluginOutcome(jsonOutput)?.pluginId).toBe("demo"); + expect(pluginOutcome(jsonOutput)?.status).toBe("skipped"); }); it("fails unexpected post-core plugin sync exceptions", async () => {