diff --git a/src/commands/sessions.test-helpers.ts b/src/commands/sessions.test-helpers.ts index 8b5a3357cdf..0c1cbd7fd62 100644 --- a/src/commands/sessions.test-helpers.ts +++ b/src/commands/sessions.test-helpers.ts @@ -5,23 +5,26 @@ import path from "node:path"; import { vi } from "vitest"; import type { RuntimeEnv } from "../runtime.js"; +const sessionsConfigState = vi.hoisted(() => ({ + loadConfig: () => ({ + agents: { + defaults: { + model: { primary: "pi:opus" }, + models: { "pi:opus": {} }, + contextTokens: 32000, + }, + }, + }), +})); + +vi.mock("../config/config.js", () => ({ + loadConfig: () => sessionsConfigState.loadConfig(), +})); + export function mockSessionsConfig() { - vi.mock("../config/config.js", async () => { - const actual = - await vi.importActual("../config/config.js"); - return { - ...actual, - loadConfig: () => ({ - agents: { - defaults: { - model: { primary: "pi:opus" }, - models: { "pi:opus": {} }, - contextTokens: 32000, - }, - }, - }), - }; - }); + // The shared config mock is hoisted above so tests can keep their + // existing setup call without paying `importActual` cost or nested-mock + // warnings before importing `sessions.ts`. } export function makeRuntime(params?: { throwOnError?: boolean }): { diff --git a/src/daemon/launchd.test.ts b/src/daemon/launchd.test.ts index 887aed55b98..1a47e7a17a3 100644 --- a/src/daemon/launchd.test.ts +++ b/src/daemon/launchd.test.ts @@ -51,6 +51,23 @@ const cleanStaleGatewayProcessesSync = vi.hoisted(() => ); const defaultProgramArguments = ["node", "-e", "process.exit(0)"]; +async function runStopLaunchAgentWithFakeTimers(args: Parameters[0]) { + vi.useFakeTimers(); + try { + const stopPromise = stopLaunchAgent(args) + .then(() => ({ ok: true as const })) + .catch((error: unknown) => ({ ok: false as const, error })); + await vi.runAllTimersAsync(); + const result = await stopPromise; + if (!result.ok) { + throw result.error; + } + return; + } finally { + vi.useRealTimers(); + } +} + function expectLaunchctlEnableBootstrapOrder(env: Record) { const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; const label = "ai.openclaw.gateway"; @@ -546,7 +563,7 @@ describe("launchd install", () => { output += chunk.toString(); }); - await stopLaunchAgent({ env, stdout }); + await runStopLaunchAgentWithFakeTimers({ env, stdout }); expect(state.launchctlCalls.some((call) => call[0] === "stop")).toBe(true); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(true); @@ -564,7 +581,7 @@ describe("launchd install", () => { output += chunk.toString(); }); - await stopLaunchAgent({ env, stdout }); + await runStopLaunchAgentWithFakeTimers({ env, stdout }); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(true); expect(output).toContain("Stopped LaunchAgent (degraded)"); @@ -597,7 +614,7 @@ describe("launchd install", () => { output += chunk.toString(); }); - await stopLaunchAgent({ env, stdout }); + await runStopLaunchAgentWithFakeTimers({ env, stdout }); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(true); expect(output).toContain("Stopped LaunchAgent (degraded)"); @@ -610,7 +627,9 @@ describe("launchd install", () => { state.printFailuresRemaining = 10; state.bootoutError = "launchctl bootout permission denied"; - await expect(stopLaunchAgent({ env, stdout: new PassThrough() })).rejects.toThrow( + await expect( + runStopLaunchAgentWithFakeTimers({ env, stdout: new PassThrough() }), + ).rejects.toThrow( "launchctl print could not confirm stop; used bootout fallback and left service unloaded: launchctl print permission denied; launchctl bootout failed: launchctl bootout permission denied", ); });