


























@@ -380,6 +380,23 @@ describe("update-cli", () => {
380380return calls[index];
381381};
382382383+const syncPluginCall = (index = 0) => {
384+const calls = syncPluginsForUpdateChannel.mock.calls as unknown as Array<
385+[{ channel?: string; config?: OpenClawConfig }]
386+>;
387+return calls[index]?.[0];
388+};
389+390+const npmPluginUpdateCall = (index = 0) => {
391+const calls = updateNpmInstalledPlugins.mock.calls as unknown as Array<
392+[{ timeoutMs?: number }]
393+>;
394+return calls[index]?.[0];
395+};
396+397+const pluginWarning = (result?: UpdateRunResult) => result?.postUpdate?.plugins?.warnings?.[0];
398+const pluginOutcome = (result?: UpdateRunResult) => result?.postUpdate?.plugins?.npm.outcomes[0];
399+383400const expectPackageInstallSpec = (spec: string) => {
384401expect(runGatewayUpdate).not.toHaveBeenCalled();
385402const call = (
@@ -799,18 +816,13 @@ describe("update-cli", () => {
799816800817await updateCommand({ channel: "dev", yes: true, restart: false });
801818802-expect(spawn).toHaveBeenCalledWith(
803-expect.stringMatching(/node/),
804-[entrypoints[0], "update", "--no-restart", "--yes"],
805-expect.objectContaining({
806-stdio: "inherit",
807-env: expect.objectContaining({
808-OPENCLAW_UPDATE_POST_CORE: "1",
809-OPENCLAW_UPDATE_POST_CORE_CHANNEL: "dev",
810-OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL: "dev",
811-}),
812-}),
813-);
819+const call = spawnCall();
820+expect(call?.[0]).toMatch(/node/);
821+expect(call?.[1]).toEqual([entrypoints[0], "update", "--no-restart", "--yes"]);
822+expect(call?.[2]?.stdio).toBe("inherit");
823+expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE).toBe("1");
824+expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE_CHANNEL).toBe("dev");
825+expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL).toBe("dev");
814826expect(replaceConfigFile).not.toHaveBeenCalled();
815827expect(syncPluginsForUpdateChannel).not.toHaveBeenCalled();
816828expect(updateNpmInstalledPlugins).not.toHaveBeenCalled();
@@ -891,10 +903,10 @@ describe("update-cli", () => {
891903);
892904893905expect(runGatewayUpdate).not.toHaveBeenCalled();
894-expect(runCommandWithTimeout).not.toHaveBeenCalledWith(
895-["npm", "i", "-g", expect.any(String)],
896- expect.anything(),
897-);
906+const installCall = (
907+vi.mocked(runCommandWithTimeout).mock.calls as unknown as Array<[string[], unknown]>
908+).find(([argv]) => argv[0] === "npm" && argv[1] === "i" && argv[2] === "-g");
909+expect(installCall).toBeUndefined();
898910expect(defaultRuntime.exit).toHaveBeenCalledWith(0);
899911expect(syncPluginsForUpdateChannel).toHaveBeenCalledTimes(1);
900912expect(updateNpmInstalledPlugins).toHaveBeenCalledTimes(1);
@@ -999,14 +1011,8 @@ describe("update-cli", () => {
9991011},
10001012baseHash: "stable-hash",
10011013});
1002-expect(syncPluginsForUpdateChannel).toHaveBeenCalledWith(
1003-expect.objectContaining({
1004-channel: "dev",
1005-config: expect.objectContaining({
1006-update: expect.objectContaining({ channel: "dev" }),
1007-}),
1008-}),
1009-);
1014+expect(syncPluginCall()?.channel).toBe("dev");
1015+expect(syncPluginCall()?.config?.update?.channel).toBe("dev");
10101016});
1011101710121018it("post-core resume mode retries update channel persistence after config hash drift", async () => {
@@ -1071,14 +1077,8 @@ describe("update-cli", () => {
10711077},
10721078baseHash: "newer-hash",
10731079});
1074-expect(syncPluginsForUpdateChannel).toHaveBeenCalledWith(
1075-expect.objectContaining({
1076-config: expect.objectContaining({
1077-meta: expect.objectContaining({ lastTouchedVersion: "2026.4.30" }),
1078-update: expect.objectContaining({ channel: "dev" }),
1079-}),
1080-}),
1081-);
1080+expect(syncPluginCall()?.config?.meta?.lastTouchedVersion).toBe("2026.4.30");
1081+expect(syncPluginCall()?.config?.update?.channel).toBe("dev");
10821082});
1083108310841084it("passes the update timeout budget into post-core plugin updates", async () => {
@@ -1092,9 +1092,7 @@ describe("update-cli", () => {
10921092},
10931093);
109410941095-expect(updateNpmInstalledPlugins).toHaveBeenCalledWith(
1096-expect.objectContaining({ timeoutMs: 1_800_000 }),
1097-);
1095+expect(npmPluginUpdateCall()?.timeoutMs).toBe(1_800_000);
10981096});
1099109711001098it("uses a fail-closed integrity policy for post-core plugin updates", async () => {
@@ -1200,16 +1198,12 @@ describe("update-cli", () => {
12001198},
12011199]);
12021200expect(jsonOutput?.postUpdate?.plugins?.status).toBe("warning");
1203-expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]).toMatchObject({
1204-pluginId: "demo",
1205-guidance: [
1206-"Run openclaw doctor --fix to attempt automatic repair.",
1207-"Run openclaw plugins inspect demo --runtime --json for details.",
1208-],
1209-});
1210-expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]?.reason).toContain(
1211-"npm package integrity drift",
1212-);
1201+expect(pluginWarning(jsonOutput)?.pluginId).toBe("demo");
1202+expect(pluginWarning(jsonOutput)?.guidance).toEqual([
1203+"Run openclaw doctor --fix to attempt automatic repair.",
1204+"Run openclaw plugins inspect demo --runtime --json for details.",
1205+]);
1206+expect(pluginWarning(jsonOutput)?.reason).toContain("npm package integrity drift");
12131207expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]?.status).toBe("error");
12141208expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]?.message).toContain(
12151209"Run openclaw doctor --fix to attempt automatic repair.",
@@ -1268,16 +1262,10 @@ describe("update-cli", () => {
12681262| undefined;
12691263expect(jsonOutput?.status).toBe("ok");
12701264expect(jsonOutput?.postUpdate?.plugins?.status).toBe("warning");
1271-expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]).toMatchObject({
1272-pluginId: "demo",
1273-});
1274-expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]?.reason).toContain(
1275-"package.json is missing",
1276-);
1277-expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]).toMatchObject({
1278-pluginId: "demo",
1279-status: "error",
1280-});
1265+expect(pluginWarning(jsonOutput)?.pluginId).toBe("demo");
1266+expect(pluginWarning(jsonOutput)?.reason).toContain("package.json is missing");
1267+expect(pluginOutcome(jsonOutput)?.pluginId).toBe("demo");
1268+expect(pluginOutcome(jsonOutput)?.status).toBe("error");
12811269});
1282127012831271it("prints non-fatal plugin warnings in human update output", async () => {
@@ -1335,17 +1323,13 @@ describe("update-cli", () => {
13351323| UpdateRunResult
13361324| undefined;
13371325expect(jsonOutput?.postUpdate?.plugins?.status).toBe("warning");
1338-expect(jsonOutput?.postUpdate?.plugins?.warnings?.[0]).toMatchObject({
1339-pluginId: "demo",
1340-guidance: [
1341-"Run openclaw doctor --fix to attempt automatic repair.",
1342-"Run openclaw plugins inspect demo --runtime --json for details.",
1343-],
1344-});
1345-expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]).toMatchObject({
1346-pluginId: "demo",
1347-status: "skipped",
1348-});
1326+expect(pluginWarning(jsonOutput)?.pluginId).toBe("demo");
1327+expect(pluginWarning(jsonOutput)?.guidance).toEqual([
1328+"Run openclaw doctor --fix to attempt automatic repair.",
1329+"Run openclaw plugins inspect demo --runtime --json for details.",
1330+]);
1331+expect(pluginOutcome(jsonOutput)?.pluginId).toBe("demo");
1332+expect(pluginOutcome(jsonOutput)?.status).toBe("skipped");
13491333});
1350133413511335it("fails unexpected post-core plugin sync exceptions", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。