From b825c8d34b7d1c419f6883fc5f7cfe7939f8197f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 01:10:28 +0100 Subject: [PATCH] test: fix full ci suite follow-ups --- src/cli/update-cli.test.ts | 60 +++++++++++++++++++++++------ test/vitest-scoped-config.test.ts | 28 ++++++++++++++ test/vitest/vitest.shared.config.ts | 6 ++- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index e60bbd18a07..700c26e2fea 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -25,6 +25,7 @@ const serviceLoaded = vi.fn(); const prepareRestartScript = vi.fn(); const runRestartScript = vi.fn(); const mockedRunDaemonInstall = vi.fn(); +const serviceReadCommand = vi.fn(); const serviceReadRuntime = vi.fn(); const inspectPortUsage = vi.fn(); const classifyPortListener = vi.fn(); @@ -164,8 +165,27 @@ vi.mock("../plugins/installed-plugin-index-records.js", async (importOriginal) = }); vi.mock("../daemon/service.js", () => ({ + readGatewayServiceState: async () => { + const command = await serviceReadCommand(); + const env = { + ...process.env, + ...(command && typeof command === "object" && "environment" in command + ? (command.environment as NodeJS.ProcessEnv | undefined) + : undefined), + }; + const [loaded, runtime] = await Promise.all([serviceLoaded({ env }), serviceReadRuntime(env)]); + return { + installed: command !== null, + loaded, + running: runtime?.status === "running", + env, + command, + runtime, + }; + }, resolveGatewayService: vi.fn(() => ({ isLoaded: (...args: unknown[]) => serviceLoaded(...args), + readCommand: (...args: unknown[]) => serviceReadCommand(...args), readRuntime: (...args: unknown[]) => serviceReadRuntime(...args), })), })); @@ -451,6 +471,9 @@ describe("update-cli", () => { readPackageVersion.mockResolvedValue("1.0.0"); resolveGlobalManager.mockResolvedValue("npm"); serviceLoaded.mockResolvedValue(false); + serviceReadCommand.mockImplementation(async () => + (await serviceLoaded()) ? { programArguments: ["openclaw", "gateway", "run"] } : null, + ); serviceReadRuntime.mockResolvedValue({ status: "running", pid: 4242, @@ -543,11 +566,12 @@ describe("update-cli", () => { }); it("keeps downgrade post-update work in the current process", async () => { + const downgradedRoot = createCaseDir("openclaw-downgraded-root"); setupUpdatedRootRefresh({ gatewayUpdateImpl: async () => makeOkUpdateResult({ mode: "npm", - root: createCaseDir("openclaw-downgraded-root"), + root: downgradedRoot, before: { version: "2026.4.14" }, after: { version: "2026.4.10" }, }), @@ -574,13 +598,13 @@ describe("update-cli", () => { url: "ws://127.0.0.1:18789", }); - await updateCommand({ yes: true, tag: "2026.4.10" }); + await updateCommand({ yes: true, tag: "2026.4.10", restart: false }); expect(spawn).not.toHaveBeenCalled(); expect(syncPluginsForUpdateChannel).toHaveBeenCalled(); expect(updateNpmInstalledPlugins).toHaveBeenCalled(); - expect(runDaemonInstall).toHaveBeenCalled(); - expect(probeGateway).toHaveBeenCalled(); + expect(runDaemonInstall).not.toHaveBeenCalled(); + expect(probeGateway).not.toHaveBeenCalled(); expect(defaultRuntime.exit).not.toHaveBeenCalledWith(1); }); @@ -1872,25 +1896,32 @@ describe("update-cli", () => { await updateCommand({ yes: true }); - expect(runDaemonInstall).toHaveBeenCalledWith({ - force: true, - json: undefined, - }); + expect(runDaemonInstall).not.toHaveBeenCalled(); expect(runRestartScript).not.toHaveBeenCalled(); expect(defaultRuntime.exit).toHaveBeenCalledWith(1); + expect( + vi + .mocked(defaultRuntime.log) + .mock.calls.map((call) => String(call[0])) + .join("\n"), + ).toContain("updated install entrypoint not found"); }); it("fails a JSON package update when fallback restart leaves the old gateway running", async () => { + const updatedRoot = createCaseDir("openclaw-updated-root"); + const updatedEntrypoint = path.join(updatedRoot, "dist", "entry.js"); setupUpdatedRootRefresh({ + entrypoints: [updatedEntrypoint], gatewayUpdateImpl: async () => makeOkUpdateResult({ mode: "npm", - root: createCaseDir("openclaw-updated-root"), + root: updatedRoot, before: { version: "2026.4.23" }, after: { version: "2026.4.24" }, }), }); prepareRestartScript.mockResolvedValue(null); + serviceLoaded.mockResolvedValue(true); probeGateway.mockResolvedValue({ ok: true, close: null, @@ -1911,7 +1942,11 @@ describe("update-cli", () => { await updateCommand({ yes: true, json: true }); expect(runRestartScript).not.toHaveBeenCalled(); - expect(runDaemonRestart).toHaveBeenCalled(); + expect(runDaemonRestart).not.toHaveBeenCalled(); + expect(runCommandWithTimeout).toHaveBeenCalledWith( + [expect.stringMatching(/node/), updatedEntrypoint, "gateway", "restart", "--json"], + expect.objectContaining({ cwd: updatedRoot, timeoutMs: 60_000 }), + ); expect(probeGateway).toHaveBeenCalledWith(expect.objectContaining({ includeDetails: true })); expect(defaultRuntime.exit).toHaveBeenCalledWith(1); expect(defaultRuntime.writeJson).not.toHaveBeenCalled(); @@ -1927,11 +1962,14 @@ describe("update-cli", () => { }); it("fails a package update when the restarted gateway reports activated plugin load errors", async () => { + const updatedRoot = createCaseDir("openclaw-updated-root"); + const updatedEntrypoint = path.join(updatedRoot, "dist", "entry.js"); setupUpdatedRootRefresh({ + entrypoints: [updatedEntrypoint], gatewayUpdateImpl: async () => makeOkUpdateResult({ mode: "npm", - root: createCaseDir("openclaw-updated-root"), + root: updatedRoot, before: { version: "2026.4.23" }, after: { version: "2026.4.24" }, }), diff --git a/test/vitest-scoped-config.test.ts b/test/vitest-scoped-config.test.ts index d1552272286..9cee32e5ea2 100644 --- a/test/vitest-scoped-config.test.ts +++ b/test/vitest-scoped-config.test.ts @@ -69,6 +69,12 @@ import { createUtilsVitestConfig } from "./vitest/vitest.utils.config.ts"; import { createWizardVitestConfig } from "./vitest/vitest.wizard.config.ts"; const EXTENSIONS_CHANNEL_GLOB = ["extensions", "channel", "**"].join("/"); +const PRIVATE_PLUGIN_SDK_SUBPATHS = [ + "qa-channel", + "qa-channel-protocol", + "qa-lab", + "qa-runtime", +] as const; function bundledExcludePatternCouldMatchFile(pattern: string, file: string): boolean { if (pattern === file) { @@ -82,6 +88,28 @@ function bundledExcludePatternCouldMatchFile(pattern: string, file: string): boo } describe("resolveVitestIsolation", () => { + it("aliases private QA plugin SDK subpaths for source tests only", () => { + expect(sharedVitestConfig.resolve.alias).toEqual( + expect.arrayContaining( + PRIVATE_PLUGIN_SDK_SUBPATHS.map((subpath) => + expect.objectContaining({ + find: `openclaw/plugin-sdk/${subpath}`, + replacement: path.join(process.cwd(), "src", "plugin-sdk", `${subpath}.ts`), + }), + ), + ), + ); + expect(sharedVitestConfig.resolve.alias).not.toEqual( + expect.arrayContaining( + PRIVATE_PLUGIN_SDK_SUBPATHS.map((subpath) => + expect.objectContaining({ + find: `@openclaw/plugin-sdk/${subpath}`, + }), + ), + ), + ); + }); + it("defaults shared scoped configs to the non-isolated runner", () => { expect(resolveVitestIsolation({})).toBe(false); }); diff --git a/test/vitest/vitest.shared.config.ts b/test/vitest/vitest.shared.config.ts index da298c03a3d..e186781ccde 100644 --- a/test/vitest/vitest.shared.config.ts +++ b/test/vitest/vitest.shared.config.ts @@ -1,6 +1,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { pluginSdkSubpaths } from "../../scripts/lib/plugin-sdk-entries.mjs"; +import privateLocalOnlyPluginSdkSubpaths from "../../scripts/lib/plugin-sdk-private-local-only-subpaths.json" with { type: "json" }; import { detectVitestHostInfo as detectVitestHostInfoImpl, isCiLikeEnv, @@ -113,6 +114,9 @@ const workerConfig = resolveSharedVitestWorkerConfig({ isWindows, localScheduling, }); +const sourcePluginSdkSubpaths = [ + ...new Set([...pluginSdkSubpaths, ...privateLocalOnlyPluginSdkSubpaths]), +].toSorted((left, right) => left.localeCompare(right)); if (!isCI && localScheduling.throttledBySystem && shouldPrintVitestThrottle(process.env)) { console.error( @@ -131,7 +135,7 @@ export const sharedVitestConfig = { find: "openclaw/extension-api", replacement: path.join(repoRoot, "src", "extensionAPI.ts"), }, - ...pluginSdkSubpaths.map((subpath) => ({ + ...sourcePluginSdkSubpaths.map((subpath) => ({ find: `openclaw/plugin-sdk/${subpath}`, replacement: path.join(repoRoot, "src", "plugin-sdk", `${subpath}.ts`), })),