mirror of https://github.com/openclaw/openclaw.git
Preserve no-restart during update doctor fixes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
3359dcfdcf
commit
d8aada9d45
|
|
@ -1,4 +1,5 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createDoctorPrompter } from "./doctor-prompter.js";
|
||||
|
||||
const service = vi.hoisted(() => ({
|
||||
isLoaded: vi.fn(),
|
||||
|
|
@ -99,6 +100,7 @@ vi.mock("./health.js", () => ({
|
|||
describe("maybeRepairGatewayDaemon", () => {
|
||||
let maybeRepairGatewayDaemon: typeof import("./doctor-gateway-daemon-flow.js").maybeRepairGatewayDaemon;
|
||||
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
|
||||
const originalUpdateInProgress = process.env.OPENCLAW_UPDATE_IN_PROGRESS;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ maybeRepairGatewayDaemon } = await import("./doctor-gateway-daemon-flow.js"));
|
||||
|
|
@ -121,6 +123,11 @@ describe("maybeRepairGatewayDaemon", () => {
|
|||
if (originalPlatformDescriptor) {
|
||||
Object.defineProperty(process, "platform", originalPlatformDescriptor);
|
||||
}
|
||||
if (originalUpdateInProgress === undefined) {
|
||||
delete process.env.OPENCLAW_UPDATE_IN_PROGRESS;
|
||||
} else {
|
||||
process.env.OPENCLAW_UPDATE_IN_PROGRESS = originalUpdateInProgress;
|
||||
}
|
||||
});
|
||||
|
||||
function setPlatform(platform: NodeJS.Platform) {
|
||||
|
|
@ -191,4 +198,44 @@ describe("maybeRepairGatewayDaemon", () => {
|
|||
expect(sleep).not.toHaveBeenCalled();
|
||||
expect(healthCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips gateway install during non-interactive update repairs", async () => {
|
||||
setPlatform("linux");
|
||||
process.env.OPENCLAW_UPDATE_IN_PROGRESS = "1";
|
||||
service.isLoaded.mockResolvedValue(false);
|
||||
|
||||
await maybeRepairGatewayDaemon({
|
||||
cfg: { gateway: {} },
|
||||
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||
prompter: createDoctorPrompter({
|
||||
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||
options: { repair: true, nonInteractive: true },
|
||||
}),
|
||||
options: { deep: false, repair: true, nonInteractive: true },
|
||||
gatewayDetailsMessage: "details",
|
||||
healthOk: false,
|
||||
});
|
||||
|
||||
expect(service.install).not.toHaveBeenCalled();
|
||||
expect(service.restart).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips gateway restart during non-interactive update repairs", async () => {
|
||||
setPlatform("linux");
|
||||
process.env.OPENCLAW_UPDATE_IN_PROGRESS = "1";
|
||||
|
||||
await maybeRepairGatewayDaemon({
|
||||
cfg: { gateway: {} },
|
||||
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||
prompter: createDoctorPrompter({
|
||||
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
|
||||
options: { repair: true, nonInteractive: true },
|
||||
}),
|
||||
options: { deep: false, repair: true, nonInteractive: true },
|
||||
gatewayDetailsMessage: "details",
|
||||
healthOk: false,
|
||||
});
|
||||
|
||||
expect(service.restart).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ vi.mock("@clack/prompts", () => ({
|
|||
|
||||
describe("createDoctorPrompter", () => {
|
||||
const originalStdinIsTTY = process.stdin.isTTY;
|
||||
const originalUpdateInProgress = process.env.OPENCLAW_UPDATE_IN_PROGRESS;
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
|
|
@ -18,6 +19,11 @@ describe("createDoctorPrompter", () => {
|
|||
value: originalStdinIsTTY,
|
||||
configurable: true,
|
||||
});
|
||||
if (originalUpdateInProgress === undefined) {
|
||||
delete process.env.OPENCLAW_UPDATE_IN_PROGRESS;
|
||||
} else {
|
||||
process.env.OPENCLAW_UPDATE_IN_PROGRESS = originalUpdateInProgress;
|
||||
}
|
||||
});
|
||||
|
||||
it("auto-accepts repairs in non-interactive fix mode", async () => {
|
||||
|
|
@ -86,6 +92,40 @@ describe("createDoctorPrompter", () => {
|
|||
expect(confirmMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps skip-in-non-interactive prompts disabled during update-mode repairs", async () => {
|
||||
Object.defineProperty(process.stdin, "isTTY", {
|
||||
value: false,
|
||||
configurable: true,
|
||||
});
|
||||
process.env.OPENCLAW_UPDATE_IN_PROGRESS = "1";
|
||||
|
||||
const prompter = createDoctorPrompter({
|
||||
runtime: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
},
|
||||
options: {
|
||||
repair: true,
|
||||
nonInteractive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
prompter.confirmRepair({
|
||||
message: "Repair gateway service config?",
|
||||
initialValue: false,
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
await expect(
|
||||
prompter.confirmSkipInNonInteractive({
|
||||
message: "Restart gateway service now?",
|
||||
initialValue: true,
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
expect(confirmMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("auto-accepts aggressive repairs only with --force in non-interactive fix mode", async () => {
|
||||
Object.defineProperty(process.stdin, "isTTY", {
|
||||
value: false,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { confirm, select } from "@clack/prompts";
|
||||
import { isTruthyEnvValue } from "../infra/env.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { stylePromptHint, stylePromptMessage } from "../terminal/prompt-style.js";
|
||||
import { guardCancel } from "./onboard-helpers.js";
|
||||
|
|
@ -33,6 +34,7 @@ export function createDoctorPrompter(params: {
|
|||
const shouldForce = params.options.force === true;
|
||||
const isTty = Boolean(process.stdin.isTTY);
|
||||
const nonInteractive = requestedNonInteractive || (!isTty && !yes);
|
||||
const updateInProgress = isTruthyEnvValue(process.env.OPENCLAW_UPDATE_IN_PROGRESS);
|
||||
|
||||
const canPrompt = isTty && !yes && !nonInteractive;
|
||||
const confirmDefault = async (p: Parameters<typeof confirm>[0]) => {
|
||||
|
|
@ -78,7 +80,12 @@ export function createDoctorPrompter(params: {
|
|||
params.runtime,
|
||||
);
|
||||
},
|
||||
confirmSkipInNonInteractive: confirmDefault,
|
||||
confirmSkipInNonInteractive: async (p) => {
|
||||
if (updateInProgress && nonInteractive) {
|
||||
return false;
|
||||
}
|
||||
return confirmDefault(p);
|
||||
},
|
||||
select: async <T>(p: Parameters<typeof select>[0], fallback: T) => {
|
||||
if (!canPrompt || shouldRepair) {
|
||||
return fallback;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
confirm,
|
||||
createDoctorRuntime,
|
||||
mockDoctorConfigSnapshot,
|
||||
serviceInstall,
|
||||
serviceIsLoaded,
|
||||
serviceRestart,
|
||||
} from "./doctor.e2e-harness.js";
|
||||
|
||||
let doctorCommand: typeof import("./doctor.js").doctorCommand;
|
||||
let healthCommand: typeof import("./health.js").healthCommand;
|
||||
|
||||
describe("doctor command update-mode repairs", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
({ doctorCommand } = await import("./doctor.js"));
|
||||
({ healthCommand } = await import("./health.js"));
|
||||
});
|
||||
|
||||
it("skips gateway installs during non-interactive update repairs", async () => {
|
||||
mockDoctorConfigSnapshot();
|
||||
|
||||
vi.mocked(healthCommand).mockRejectedValueOnce(new Error("gateway closed"));
|
||||
|
||||
serviceIsLoaded.mockResolvedValueOnce(false);
|
||||
serviceInstall.mockClear();
|
||||
serviceRestart.mockClear();
|
||||
confirm.mockClear();
|
||||
|
||||
await doctorCommand(createDoctorRuntime(), { repair: true, nonInteractive: true });
|
||||
|
||||
expect(serviceInstall).not.toHaveBeenCalled();
|
||||
expect(serviceRestart).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips gateway restarts during non-interactive update repairs", async () => {
|
||||
mockDoctorConfigSnapshot();
|
||||
|
||||
vi.mocked(healthCommand).mockRejectedValueOnce(new Error("gateway closed"));
|
||||
|
||||
serviceIsLoaded.mockResolvedValueOnce(true);
|
||||
serviceRestart.mockClear();
|
||||
confirm.mockClear();
|
||||
|
||||
await doctorCommand(createDoctorRuntime(), { repair: true, nonInteractive: true });
|
||||
|
||||
expect(serviceRestart).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue