import process from "node:process"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const applyCliProfileEnvMock = vi.hoisted(() => vi.fn()); const attachChildProcessBridgeMock = vi.hoisted(() => vi.fn()); const installProcessWarningFilterMock = vi.hoisted(() => vi.fn()); const isMainModuleMock = vi.hoisted(() => vi.fn(() => true)); const isRootHelpInvocationMock = vi.hoisted(() => vi.fn(() => false)); const isRootVersionInvocationMock = vi.hoisted(() => vi.fn(() => true)); const normalizeEnvMock = vi.hoisted(() => vi.fn()); const normalizeWindowsArgvMock = vi.hoisted(() => vi.fn((argv: string[]) => argv)); const parseCliProfileArgsMock = vi.hoisted(() => vi.fn((argv: string[]) => ({ ok: true, argv }))); const resolveCliContainerTargetMock = vi.hoisted(() => vi.fn<() => string | null>(() => null)); const resolveCommitHashMock = vi.hoisted(() => vi.fn<() => string | null>(() => "abc1234")); const runCliMock = vi.hoisted(() => vi.fn(async () => {})); const shouldSkipRespawnForArgvMock = vi.hoisted(() => vi.fn(() => true)); vi.mock("./cli/argv.js", () => ({ isRootHelpInvocation: isRootHelpInvocationMock, isRootVersionInvocation: isRootVersionInvocationMock, })); vi.mock("./cli/container-target.js", () => ({ parseCliContainerArgs: (argv: string[]) => ({ ok: true, container: null, argv }), resolveCliContainerTarget: resolveCliContainerTargetMock, })); vi.mock("./cli/profile.js", () => ({ applyCliProfileEnv: applyCliProfileEnvMock, parseCliProfileArgs: parseCliProfileArgsMock, })); vi.mock("./cli/run-main.js", () => ({ runCli: runCliMock, })); vi.mock("./cli/respawn-policy.js", () => ({ shouldSkipRespawnForArgv: shouldSkipRespawnForArgvMock, })); vi.mock("./cli/windows-argv.js", () => ({ normalizeWindowsArgv: normalizeWindowsArgvMock, })); vi.mock("./infra/env.js", () => ({ isTruthyEnvValue: () => false, normalizeEnv: normalizeEnvMock, })); vi.mock("./infra/git-commit.js", () => ({ resolveCommitHash: resolveCommitHashMock, })); vi.mock("./infra/is-main.js", () => ({ isMainModule: isMainModuleMock, })); vi.mock("./infra/warning-filter.js", () => ({ installProcessWarningFilter: installProcessWarningFilterMock, })); vi.mock("./process/child-process-bridge.js", () => ({ attachChildProcessBridge: attachChildProcessBridgeMock, })); vi.mock("./version.js", () => ({ VERSION: "9.9.9-test", })); describe("entry root version fast path", () => { let originalArgv: string[]; let originalGatewayToken: string | undefined; let exitSpy: ReturnType; beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); originalArgv = [...process.argv]; originalGatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN; delete process.env.OPENCLAW_GATEWAY_TOKEN; process.argv = ["node", "openclaw", "--version"]; exitSpy = vi .spyOn(process, "exit") .mockImplementation(((_code?: number) => undefined) as typeof process.exit); }); afterEach(() => { process.argv = originalArgv; if (originalGatewayToken === undefined) { delete process.env.OPENCLAW_GATEWAY_TOKEN; } else { process.env.OPENCLAW_GATEWAY_TOKEN = originalGatewayToken; } exitSpy.mockRestore(); }); it("prints commit-tagged version output when commit metadata is available", async () => { const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await import("./entry.js"); await vi.waitFor(() => { expect(logSpy).toHaveBeenCalledWith("OpenClaw 9.9.9-test (abc1234)"); expect(exitSpy).toHaveBeenCalledWith(0); }); logSpy.mockRestore(); }); it("falls back to plain version output when commit metadata is unavailable", async () => { resolveCommitHashMock.mockReturnValueOnce(null); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await import("./entry.js"); await vi.waitFor(() => { expect(logSpy).toHaveBeenCalledWith("OpenClaw 9.9.9-test"); expect(exitSpy).toHaveBeenCalledWith(0); }); logSpy.mockRestore(); }); it("skips the host version fast path when a container target is active", async () => { resolveCliContainerTargetMock.mockReturnValue("demo"); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await import("./entry.js"); await vi.waitFor(() => { expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]); }); expect(logSpy).not.toHaveBeenCalled(); expect(exitSpy).not.toHaveBeenCalled(); logSpy.mockRestore(); }); it("rejects container mode for root version when gateway override env vars are set", async () => { resolveCliContainerTargetMock.mockReturnValue("demo"); process.env.OPENCLAW_GATEWAY_TOKEN = "demo-token"; const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); await import("./entry.js"); await vi.waitFor(() => { expect(errorSpy).toHaveBeenCalledWith( "[openclaw] --container cannot be combined with --profile/--dev or gateway override env vars", ); expect(exitSpy).toHaveBeenCalledWith(2); }); expect(runCliMock).not.toHaveBeenCalled(); errorSpy.mockRestore(); }); });