From e9c7a64c5edbedbe00daf2cedbb2f1f8b1991702 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 1 Jun 2026 05:52:03 +0200 Subject: [PATCH] refactor: share update test helpers --- src/gateway/server-methods/update.test.ts | 148 ++++++++-------------- 1 file changed, 52 insertions(+), 96 deletions(-) diff --git a/src/gateway/server-methods/update.test.ts b/src/gateway/server-methods/update.test.ts index f90807cba88..faceee0c18f 100644 --- a/src/gateway/server-methods/update.test.ts +++ b/src/gateway/server-methods/update.test.ts @@ -27,6 +27,14 @@ const startManagedServiceUpdateHandoffMock = vi.fn(async () => ({ const scheduleGatewaySigusr1RestartMock = vi.fn(() => ({ scheduled: true })); +type UpdateRunPayload = { + ok: boolean; + result?: { status?: string; reason?: string; mode?: string }; + handoff?: { status?: string; command?: string; message?: string }; + sentinel?: { path?: string | null }; + restart?: unknown; +}; + vi.mock("../../config/config.js", () => ({ getRuntimeConfig: () => ({ update: {} }), })); @@ -172,6 +180,16 @@ async function invokeUpdateRun( } as never); } +async function captureUpdateRunPayload( + params: Record = {}, +): Promise { + let payload: UpdateRunPayload | undefined; + await invokeUpdateRun(params, (_ok: boolean, response: unknown) => { + payload = response as UpdateRunPayload; + }); + return payload; +} + function readCapturedPayload(): RestartSentinelPayload { if (!capturedPayload) { throw new Error("expected restart sentinel payload"); @@ -190,6 +208,24 @@ function firstMockCall( return call; } +function mockGlobalInstallSurface() { + resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ + kind: "global", + mode: "npm", + root: "/tmp/openclaw-global", + packageRoot: "/tmp/openclaw-global", + }); +} + +function mockGitInstallSurface(root: string) { + resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ + kind: "git", + mode: "git", + root, + packageRoot: root, + }); +} + describe("update.run sentinel deliveryContext", () => { it("includes deliveryContext in sentinel payload when sessionKey is provided", async () => { capturedPayload = undefined; @@ -264,12 +300,7 @@ describe("update.run timeout normalization", () => { describe("update.run restart scheduling", () => { it("schedules restart when update succeeds", async () => { - let payload: { ok: boolean; restart: unknown } | undefined; - - await invokeUpdateRun({}, (_ok: boolean, response: unknown) => { - const typed = response as { ok: boolean; restart: unknown }; - payload = typed; - }); + const payload = await captureUpdateRunPayload(); expect(scheduleGatewaySigusr1RestartMock).toHaveBeenCalledTimes(1); expect(payload?.ok).toBe(true); @@ -285,18 +316,10 @@ describe("update.run restart scheduling", () => { durationMs: 100, }); - let payload: { ok: boolean; restart: unknown } | undefined; - - await invokeUpdateRun( - { - sessionKey: "agent:main:webchat:dm:user-123", - continuationMessage: "This should not run after a failed update.", - }, - (_ok: boolean, response: unknown) => { - const typed = response as { ok: boolean; restart: unknown }; - payload = typed; - }, - ); + const payload = await captureUpdateRunPayload({ + sessionKey: "agent:main:webchat:dm:user-123", + continuationMessage: "This should not run after a failed update.", + }); expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled(); expect(payload?.ok).toBe(false); @@ -320,11 +343,7 @@ describe("update.run restart scheduling", () => { durationMs: 100, }); - let payload: { ok: boolean; result?: { status?: string; reason?: string } } | undefined; - - await invokeUpdateRun({}, (_ok: boolean, response: unknown) => { - payload = response as typeof payload; - }); + const payload = await captureUpdateRunPayload(); expect(payload?.ok).toBe(false); expect(payload?.result?.status).toBe(status); @@ -333,24 +352,9 @@ describe("update.run restart scheduling", () => { it("hands managed package updates to the CLI path instead of running them in-process", async () => { detectRespawnSupervisorMock.mockReturnValueOnce("launchd"); - resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ - kind: "global", - mode: "npm", - root: "/tmp/openclaw-global", - packageRoot: "/tmp/openclaw-global", - }); + mockGlobalInstallSurface(); - let payload: - | { - ok: boolean; - result?: { status?: string; reason?: string; mode?: string }; - sentinel?: { path?: string | null }; - } - | undefined; - - await invokeUpdateRun({}, (_ok: boolean, response: unknown) => { - payload = response as typeof payload; - }); + const payload = await captureUpdateRunPayload(); expect(runGatewayUpdateMock).not.toHaveBeenCalled(); expect(startManagedServiceUpdateHandoffMock).toHaveBeenCalledTimes(1); @@ -410,12 +414,7 @@ describe("update.run restart scheduling", () => { it("keeps a startup grace before restarting after systemd handoff spawn", async () => { detectRespawnSupervisorMock.mockReturnValueOnce("systemd"); - resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ - kind: "global", - mode: "npm", - root: "/tmp/openclaw-global", - packageRoot: "/tmp/openclaw-global", - }); + mockGlobalInstallSurface(); await invokeUpdateRun({ restartDelayMs: 0 }); @@ -437,12 +436,7 @@ describe("update.run restart scheduling", () => { it("starts managed package handoff when the gateway cwd is unavailable", async () => { detectRespawnSupervisorMock.mockReturnValueOnce("launchd"); - resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ - kind: "global", - mode: "npm", - root: "/tmp/openclaw-global", - packageRoot: "/tmp/openclaw-global", - }); + mockGlobalInstallSurface(); const cwdSpy = vi.spyOn(process, "cwd").mockImplementation(() => { throw Object.assign(new Error("uv_cwd"), { code: "ENOENT", syscall: "uv_cwd" }); }); @@ -468,20 +462,9 @@ describe("update.run restart scheduling", () => { steps: [], durationMs: 100, }); - resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ - kind: "git", - mode: "git", - root: "/tmp/openclaw-git", - packageRoot: "/tmp/openclaw-git", - }); + mockGitInstallSurface("/tmp/openclaw-git"); - let payload: - | { ok: boolean; result?: { status?: string; mode?: string }; handoff?: unknown } - | undefined; - - await invokeUpdateRun({}, (_ok: boolean, response: unknown) => { - payload = response as typeof payload; - }); + const payload = await captureUpdateRunPayload(); expect(runGatewayUpdateMock).toHaveBeenCalledTimes(1); expect(startManagedServiceUpdateHandoffMock).not.toHaveBeenCalled(); @@ -493,25 +476,9 @@ describe("update.run restart scheduling", () => { }); it("returns a safe command when package updates cannot be handed off", async () => { - resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ - kind: "global", - mode: "npm", - root: "/tmp/openclaw-global", - packageRoot: "/tmp/openclaw-global", - }); + mockGlobalInstallSurface(); - let payload: - | { - ok: boolean; - result?: { status?: string; reason?: string; mode?: string }; - handoff?: { status?: string; command?: string; message?: string }; - restart?: unknown; - } - | undefined; - - await invokeUpdateRun({ timeoutMs: 1_800_000 }, (_ok: boolean, response: unknown) => { - payload = response as typeof payload; - }); + const payload = await captureUpdateRunPayload({ timeoutMs: 1_800_000 }); expect(runGatewayUpdateMock).not.toHaveBeenCalled(); expect(startManagedServiceUpdateHandoffMock).not.toHaveBeenCalled(); @@ -531,20 +498,9 @@ describe("update.run restart scheduling", () => { it("blocks global package installs when the gateway cannot restart afterward", async () => { isRestartEnabledMock.mockReturnValue(false); detectRespawnSupervisorMock.mockReturnValue(null); - resolveUpdateInstallSurfaceMock.mockResolvedValueOnce({ - kind: "global", - mode: "npm", - root: "/tmp/openclaw-global", - packageRoot: "/tmp/openclaw-global", - }); + mockGlobalInstallSurface(); - let payload: - | { ok: boolean; result?: { status?: string; reason?: string; mode?: string } } - | undefined; - - await invokeUpdateRun({}, (_ok: boolean, response: unknown) => { - payload = response as typeof payload; - }); + const payload = await captureUpdateRunPayload(); expect(runGatewayUpdateMock).not.toHaveBeenCalled(); expect(scheduleGatewaySigusr1RestartMock).not.toHaveBeenCalled();