diff --git a/src/cli/qr-dashboard.integration.test.ts b/src/cli/qr-dashboard.integration.test.ts index 4a6a50a95c0..234fc8d88b9 100644 --- a/src/cli/qr-dashboard.integration.test.ts +++ b/src/cli/qr-dashboard.integration.test.ts @@ -2,20 +2,38 @@ import { Command } from "commander"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { captureEnv } from "../test-utils/env.js"; -const loadConfigMock = vi.fn(); -const readConfigFileSnapshotMock = vi.fn(); -const resolveGatewayPortMock = vi.fn(() => 18789); -const copyToClipboardMock = vi.fn(async () => false); +const loadConfigMock = vi.hoisted(() => vi.fn()); +const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn()); +const resolveGatewayPortMock = vi.hoisted(() => vi.fn(() => 18789)); +const copyToClipboardMock = vi.hoisted(() => vi.fn(async () => false)); const runtimeLogs: string[] = []; const runtimeErrors: string[] = []; -const runtime = { +const runtime = vi.hoisted(() => ({ log: (message: string) => runtimeLogs.push(message), error: (message: string) => runtimeErrors.push(message), exit: (code: number) => { throw new Error(`__exit__:${code}`); }, -}; +})); + +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: loadConfigMock, + readConfigFileSnapshot: readConfigFileSnapshotMock, + resolveGatewayPort: resolveGatewayPortMock, + }; +}); + +vi.mock("../infra/clipboard.js", () => ({ + copyToClipboard: copyToClipboardMock, +})); + +vi.mock("../runtime.js", () => ({ + defaultRuntime: runtime, +})); function createGatewayTokenRefFixture() { return { @@ -75,29 +93,12 @@ async function withCliModules( }) => Promise, ): Promise { vi.resetModules(); - vi.doMock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: loadConfigMock, - readConfigFileSnapshot: readConfigFileSnapshotMock, - resolveGatewayPort: resolveGatewayPortMock, - }; - }); - vi.doMock("../infra/clipboard.js", () => ({ - copyToClipboard: copyToClipboardMock, - })); - vi.doMock("../runtime.js", () => ({ - defaultRuntime: runtime, - })); try { const { registerQrCli } = await import("./qr-cli.js"); const { registerMaintenanceCommands } = await import("./program/register.maintenance.js"); return await run({ registerQrCli, registerMaintenanceCommands }); } finally { - vi.doUnmock("../config/config.js"); - vi.doUnmock("../infra/clipboard.js"); - vi.doUnmock("../runtime.js"); + vi.restoreAllMocks(); } } diff --git a/src/cron/isolated-agent.delivery-target-thread-session.test.ts b/src/cron/isolated-agent.delivery-target-thread-session.test.ts index c5aabf79d11..bd7b8f06770 100644 --- a/src/cron/isolated-agent.delivery-target-thread-session.test.ts +++ b/src/cron/isolated-agent.delivery-target-thread-session.test.ts @@ -1,22 +1,13 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { parseTelegramTarget } from "../../extensions/telegram/src/targets.js"; import type { OpenClawConfig } from "../config/config.js"; // Mock session store so we can control what entries exist. const mockStore: Record> = {}; type DeliveryTargetModule = typeof import("./isolated-agent/delivery-target.js"); +let resolveDeliveryTarget: DeliveryTargetModule["resolveDeliveryTarget"]; -beforeEach(() => { - vi.clearAllMocks(); - for (const key of Object.keys(mockStore)) { - delete mockStore[key]; - } -}); - -async function withResolveDeliveryTarget( - run: (resolveDeliveryTarget: DeliveryTargetModule["resolveDeliveryTarget"]) => Promise, -): Promise { - vi.resetModules(); +beforeAll(async () => { vi.doMock("../config/sessions.js", () => ({ loadSessionStore: vi.fn((storePath: string) => mockStore[storePath] ?? {}), resolveAgentMainSessionKey: vi.fn( @@ -48,17 +39,22 @@ async function withResolveDeliveryTarget( })), normalizeChannelId: vi.fn((id: string) => id), })); - try { - const { resolveDeliveryTarget } = await import("./isolated-agent/delivery-target.js"); - return await run(resolveDeliveryTarget); - } finally { - vi.restoreAllMocks(); - vi.doUnmock("../config/sessions.js"); - vi.doUnmock("../infra/outbound/channel-selection.js"); - vi.doUnmock("../channels/plugins/index.js"); - vi.resetModules(); + ({ resolveDeliveryTarget } = await import("./isolated-agent/delivery-target.js")); +}); + +afterAll(() => { + vi.doUnmock("../config/sessions.js"); + vi.doUnmock("../infra/outbound/channel-selection.js"); + vi.doUnmock("../channels/plugins/index.js"); + vi.resetModules(); +}); + +beforeEach(() => { + vi.clearAllMocks(); + for (const key of Object.keys(mockStore)) { + delete mockStore[key]; } -} +}); describe("resolveDeliveryTarget thread session lookup", () => { const cfg: OpenClawConfig = {}; @@ -80,13 +76,10 @@ describe("resolveDeliveryTarget thread session lookup", () => { }, }; - const result = await withResolveDeliveryTarget( - async (resolveDeliveryTarget) => - await resolveDeliveryTarget(cfg, "main", { - channel: "last", - sessionKey: "agent:main:main:thread:9999", - }), - ); + const result = await resolveDeliveryTarget(cfg, "main", { + channel: "last", + sessionKey: "agent:main:main:thread:9999", + }); expect(result.to).toBe("-100111"); expect(result.threadId).toBe(9999); @@ -103,13 +96,10 @@ describe("resolveDeliveryTarget thread session lookup", () => { }, }; - const result = await withResolveDeliveryTarget( - async (resolveDeliveryTarget) => - await resolveDeliveryTarget(cfg, "main", { - channel: "last", - sessionKey: "agent:main:main:thread:nonexistent", - }), - ); + const result = await resolveDeliveryTarget(cfg, "main", { + channel: "last", + sessionKey: "agent:main:main:thread:nonexistent", + }); expect(result.to).toBe("-100222"); expect(result.threadId).toBeUndefined(); @@ -126,12 +116,9 @@ describe("resolveDeliveryTarget thread session lookup", () => { }, }; - const result = await withResolveDeliveryTarget( - async (resolveDeliveryTarget) => - await resolveDeliveryTarget(cfg, "main", { - channel: "last", - }), - ); + const result = await resolveDeliveryTarget(cfg, "main", { + channel: "last", + }); expect(result.to).toBe("-100333"); expect(result.threadId).toBeUndefined(); @@ -140,13 +127,10 @@ describe("resolveDeliveryTarget thread session lookup", () => { it("preserves threadId from :topic: in delivery.to on first run (no session history)", async () => { mockStore["/mock/store.json"] = {}; - const result = await withResolveDeliveryTarget( - async (resolveDeliveryTarget) => - await resolveDeliveryTarget(cfg, "main", { - channel: "telegram", - to: "63448508:topic:1008013", - }), - ); + const result = await resolveDeliveryTarget(cfg, "main", { + channel: "telegram", + to: "63448508:topic:1008013", + }); expect(result.to).toBe("63448508"); expect(result.threadId).toBe(1008013); @@ -164,14 +148,11 @@ describe("resolveDeliveryTarget thread session lookup", () => { }, }; - const result = await withResolveDeliveryTarget( - async (resolveDeliveryTarget) => - await resolveDeliveryTarget(cfg, "main", { - channel: "telegram", - to: "-100444", - accountId: "explicit-account", - }), - ); + const result = await resolveDeliveryTarget(cfg, "main", { + channel: "telegram", + to: "-100444", + accountId: "explicit-account", + }); expect(result.accountId).toBe("explicit-account"); expect(result.to).toBe("-100444"); @@ -187,13 +168,10 @@ describe("resolveDeliveryTarget thread session lookup", () => { }, }; - const result = await withResolveDeliveryTarget( - async (resolveDeliveryTarget) => - await resolveDeliveryTarget(cfg, "main", { - channel: "telegram", - to: "63448508:topic:1008013", - }), - ); + const result = await resolveDeliveryTarget(cfg, "main", { + channel: "telegram", + to: "63448508:topic:1008013", + }); expect(result.to).toBe("63448508"); expect(result.threadId).toBe(1008013); diff --git a/src/cron/service.issue-regressions.test-helpers.ts b/src/cron/service.issue-regressions.test-helpers.ts index 56117fed7c2..d6a680e21f0 100644 --- a/src/cron/service.issue-regressions.test-helpers.ts +++ b/src/cron/service.issue-regressions.test-helpers.ts @@ -2,10 +2,7 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterAll, afterEach, beforeAll, beforeEach, vi } from "vitest"; -import { clearAllBootstrapSnapshots } from "../agents/bootstrap-cache.js"; -import { clearSessionStoreCacheForTest } from "../config/sessions/store.js"; -import { resetAgentRunContextForTest } from "../infra/agent-events.js"; +import { afterAll, beforeAll, beforeEach, vi } from "vitest"; import { useFrozenTime, useRealTime } from "../test-utils/frozen-time.js"; import type { CronService } from "./service.js"; import type { CronJob, CronJobState } from "./types.js"; @@ -31,21 +28,11 @@ export function setupCronIssueRegressionFixtures() { }); beforeEach(() => { - vi.clearAllTimers(); useFrozenTime("2026-02-06T10:05:00.000Z"); }); - afterEach(() => { - vi.clearAllTimers(); - useRealTime(); - clearSessionStoreCacheForTest(); - resetAgentRunContextForTest(); - clearAllBootstrapSnapshots(); - vi.restoreAllMocks(); - vi.resetModules(); - }); - afterAll(async () => { + useRealTime(); await fs.rm(fixtureRoot, { recursive: true, force: true }); }); diff --git a/src/cron/service.issue-regressions.test.ts b/src/cron/service.issue-regressions.test.ts index 57dd3388ce7..dac28f4b0c9 100644 --- a/src/cron/service.issue-regressions.test.ts +++ b/src/cron/service.issue-regressions.test.ts @@ -1,5 +1,5 @@ import fs from "node:fs/promises"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import type { HeartbeatRunResult } from "../infra/heartbeat-wake.js"; import { clearCommandLane, setCommandLaneConcurrency } from "../process/command-queue.js"; import { CommandLane } from "../process/lanes.js"; @@ -39,16 +39,6 @@ const FAST_TIMEOUT_SECONDS = 0.0025; describe("Cron issue regressions", () => { const { makeStorePath } = setupCronIssueRegressionFixtures(); - afterEach(() => { - // Shared-state runs can begin collecting the next file before runner-level - // cleanup unwinds this suite's fake timers or command-lane mutations. - vi.clearAllTimers(); - vi.useRealTimers(); - vi.restoreAllMocks(); - clearCommandLane(CommandLane.Cron); - setCommandLaneConcurrency(CommandLane.Cron, 1); - }); - it("covers schedule updates and payload patching", async () => { const store = makeStorePath(); const cron = await startCronForStore({