
























@@ -7,6 +7,7 @@ import { Command } from "commander";
77import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
88import { TEST_BUNDLED_RUNTIME_SIDECAR_PATHS } from "../../test/helpers/bundled-runtime-sidecars.js";
99import type { OpenClawConfig, ConfigFileSnapshot } from "../config/types.openclaw.js";
10+import type { PluginInstallRecord } from "../config/types.plugins.js";
1011import { writePackageDistInventory } from "../infra/package-dist-inventory.js";
1112import { isBetaTag } from "../infra/update-channels.js";
1213import type { UpdateRunResult } from "../infra/update-runner.js";
@@ -3378,6 +3379,7 @@ describe("update-cli", () => {
33783379expect(process.env.OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE).toBeUndefined();
33793380expect(doctorCommand).toHaveBeenCalledWith(defaultRuntime, {
33803381nonInteractive: true,
3382+repair: true,
33813383yes: true,
33823384});
33833385expect(syncPluginCall()?.channel).toBe("stable");
@@ -3399,6 +3401,109 @@ describe("update-cli", () => {
33993401);
34003402});
340134033404+it("updateFinalizeCommand repairs doctor by default and refreshes plugin state after doctor", async () => {
3405+const preDoctorConfig = {
3406+update: { channel: "stable" },
3407+plugins: { entries: { pre: { enabled: true } } },
3408+} as OpenClawConfig;
3409+const postDoctorConfig = {
3410+update: { channel: "beta" },
3411+plugins: { entries: { post: { enabled: true } } },
3412+} as OpenClawConfig;
3413+const preDoctorSnapshot: ConfigFileSnapshot = {
3414+ ...baseSnapshot,
3415+sourceConfig: preDoctorConfig,
3416+resolved: preDoctorConfig,
3417+runtimeConfig: preDoctorConfig,
3418+config: preDoctorConfig,
3419+hash: "pre-doctor",
3420+};
3421+const postDoctorSnapshot: ConfigFileSnapshot = {
3422+ ...baseSnapshot,
3423+sourceConfig: postDoctorConfig,
3424+resolved: postDoctorConfig,
3425+runtimeConfig: postDoctorConfig,
3426+config: postDoctorConfig,
3427+hash: "post-doctor",
3428+};
3429+const postDoctorRecords = {
3430+"post-plugin": {
3431+source: "npm",
3432+spec: "post-plugin@1.0.0",
3433+},
3434+} satisfies Record<string, PluginInstallRecord>;
3435+vi.mocked(readConfigFileSnapshot)
3436+.mockResolvedValueOnce(preDoctorSnapshot)
3437+.mockResolvedValueOnce(postDoctorSnapshot);
3438+loadInstalledPluginIndexInstallRecords.mockResolvedValueOnce(postDoctorRecords);
3439+syncPluginsForUpdateChannel.mockImplementationOnce(
3440+async (params: { config?: OpenClawConfig }) => ({
3441+changed: true,
3442+config: params.config ?? baseConfig,
3443+summary: {
3444+switchedToBundled: [],
3445+switchedToNpm: [],
3446+warnings: [],
3447+errors: [],
3448+},
3449+}),
3450+);
3451+3452+await updateFinalizeCommand({ json: true, timeout: "9", restart: false });
3453+3454+expect(doctorCommand).toHaveBeenCalledWith(defaultRuntime, {
3455+nonInteractive: true,
3456+repair: true,
3457+yes: false,
3458+});
3459+expect(syncPluginCall()?.channel).toBe("beta");
3460+expect(syncPluginCall()?.config).toEqual({
3461+ ...postDoctorConfig,
3462+plugins: {
3463+ ...postDoctorConfig.plugins,
3464+installs: postDoctorRecords,
3465+},
3466+});
3467+expect(lastReplaceConfigCall()?.baseHash).toBe("post-doctor");
3468+expect(vi.mocked(doctorCommand).mock.invocationCallOrder[0] ?? 0).toBeLessThan(
3469+loadInstalledPluginIndexInstallRecords.mock.invocationCallOrder[0] ?? 0,
3470+);
3471+expect((lastWriteJsonCall() as { channel?: string } | undefined)?.channel).toBe("beta");
3472+});
3473+3474+it("updateFinalizeCommand reapplies requested channel against post-doctor config", async () => {
3475+const preDoctorConfig = { update: { channel: "stable" } } as OpenClawConfig;
3476+const postDoctorConfig = { update: { channel: "beta" } } as OpenClawConfig;
3477+const preDoctorSnapshot: ConfigFileSnapshot = {
3478+ ...baseSnapshot,
3479+sourceConfig: preDoctorConfig,
3480+resolved: preDoctorConfig,
3481+runtimeConfig: preDoctorConfig,
3482+config: preDoctorConfig,
3483+hash: "pre-doctor",
3484+};
3485+const postDoctorSnapshot: ConfigFileSnapshot = {
3486+ ...baseSnapshot,
3487+sourceConfig: postDoctorConfig,
3488+resolved: postDoctorConfig,
3489+runtimeConfig: postDoctorConfig,
3490+config: postDoctorConfig,
3491+hash: "post-doctor",
3492+};
3493+vi.mocked(readConfigFileSnapshot)
3494+.mockResolvedValueOnce(preDoctorSnapshot)
3495+.mockResolvedValueOnce(postDoctorSnapshot);
3496+3497+await updateFinalizeCommand({ channel: "dev", json: true, restart: false });
3498+3499+expect(replaceConfigCall(0)?.baseHash).toBe("pre-doctor");
3500+expect(replaceConfigCall(0)?.nextConfig).toEqual({ update: { channel: "dev" } });
3501+expect(replaceConfigCall(1)?.baseHash).toBe("post-doctor");
3502+expect(replaceConfigCall(1)?.nextConfig).toEqual({ update: { channel: "dev" } });
3503+expect(syncPluginCall()?.channel).toBe("dev");
3504+expect((lastWriteJsonCall() as { channel?: string } | undefined)?.channel).toBe("dev");
3505+});
3506+34023507it.each([
34033508{
34043509name: "update command invalid timeout",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。