test: speed up targeted unit suites

This commit is contained in:
Peter Steinberger 2026-03-24 19:31:02 +00:00
parent 698c02e775
commit 4029ce738c
11 changed files with 103 additions and 37 deletions

View File

@ -1,6 +1,6 @@
import type { MessageEvent, PostbackEvent } from "@line/bot-sdk";
import type { HistoryEntry } from "openclaw/plugin-sdk/reply-history";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { LineAccountConfig } from "./types.js";
// Avoid pulling in globals/pairing/media dependencies; this suite only asserts
@ -209,8 +209,12 @@ async function startInflightReplayDuplicate(params: {
}
describe("handleLineWebhookEvents", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ handleLineWebhookEvents, createLineWebhookReplayCache } = await import("./bot-handlers.js"));
});
beforeEach(() => {
buildLineMessageContextMock.mockReset();
buildLineMessageContextMock.mockImplementation(async () => ({
ctxPayload: { From: "line:group:group-1" },
@ -225,7 +229,6 @@ describe("handleLineWebhookEvents", () => {
readAllowFromStoreMock.mockImplementation(async () => [] as string[]);
upsertPairingRequestMock.mockReset();
upsertPairingRequestMock.mockImplementation(async () => ({ code: "CODE", created: true }));
({ handleLineWebhookEvents, createLineWebhookReplayCache } = await import("./bot-handlers.js"));
});
it("blocks group messages when groupPolicy is disabled", async () => {
const processMessage = vi.fn();

View File

@ -1,6 +1,6 @@
import { setTimeout as scheduleNativeTimeout } from "node:timers";
import { setTimeout as sleep } from "node:timers/promises";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import type { AcpSessionRuntimeOptions, SessionAcpMeta } from "../../config/sessions/types.js";
import type { AcpRuntime, AcpRuntimeCapabilities } from "../runtime/types.js";
@ -35,6 +35,7 @@ vi.mock("../runtime/registry.js", async (importOriginal) => {
let AcpSessionManager: typeof import("./manager.js").AcpSessionManager;
let AcpRuntimeError: typeof import("../runtime/errors.js").AcpRuntimeError;
let resetAcpSessionManagerForTests: typeof import("./manager.js").__testing.resetAcpSessionManagerForTests;
const baseCfg = {
acp: {
@ -149,10 +150,17 @@ function extractRuntimeOptionsFromUpserts(): Array<AcpSessionRuntimeOptions | un
}
describe("AcpSessionManager", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ AcpSessionManager } = await import("./manager.js"));
({
AcpSessionManager,
__testing: { resetAcpSessionManagerForTests },
} = await import("./manager.js"));
({ AcpRuntimeError } = await import("../runtime/errors.js"));
});
beforeEach(() => {
resetAcpSessionManagerForTests();
vi.useRealTimers();
hoisted.listAcpSessionEntriesMock.mockReset().mockResolvedValue([]);
hoisted.readAcpSessionEntryMock.mockReset();

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { parseFeishuConversationId } from "../../extensions/feishu/src/conversation-id.js";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import type { ChannelConfiguredBindingProvider, ChannelPlugin } from "../channels/plugins/types.js";
@ -311,7 +311,7 @@ function mockReadySession(params: {
return sessionKey;
}
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
persistentBindingsResolveModule = await import("./persistent-bindings.resolve.js");
lifecycleBindingsModule = await import("./persistent-bindings.lifecycle.js");
@ -323,6 +323,9 @@ beforeEach(async () => {
ensureConfiguredAcpBindingSession: lifecycleBindingsModule.ensureConfiguredAcpBindingSession,
resetAcpSessionInPlace: lifecycleBindingsModule.resetAcpSessionInPlace,
};
});
beforeEach(() => {
setActivePluginRegistry(
createTestRegistry([
{

View File

@ -16,8 +16,12 @@ const completeMock = vi.hoisted(() => vi.fn());
type PdfToolModule = typeof import("./pdf-tool.js");
let createPdfTool: PdfToolModule["createPdfTool"];
let resolvePdfModelConfigForTool: PdfToolModule["resolvePdfModelConfigForTool"];
let pdfToolModulePromise: Promise<PdfToolModule> | null = null;
async function importPdfToolModule(): Promise<PdfToolModule> {
if (pdfToolModulePromise) {
return await pdfToolModulePromise;
}
vi.resetModules();
vi.doMock("@mariozechner/pi-ai", async (importOriginal) => {
const actual = await importOriginal<typeof import("@mariozechner/pi-ai")>();
@ -26,7 +30,8 @@ async function importPdfToolModule(): Promise<PdfToolModule> {
complete: completeMock,
};
});
return import("./pdf-tool.js");
pdfToolModulePromise = import("./pdf-tool.js");
return await pdfToolModulePromise;
}
async function withTempAgentDir<T>(run: (agentDir: string) => Promise<T>): Promise<T> {

View File

@ -1,5 +1,5 @@
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { markdownToSignalTextChunks } from "../../../extensions/signal/src/format.js";
import {
signalOutbound,
@ -200,9 +200,12 @@ function expectSuccessfulWhatsAppInternalHookPayload(
}
describe("deliverOutboundPayloads", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ deliverOutboundPayloads, normalizeOutboundPayloads } = await import("./deliver.js"));
});
beforeEach(() => {
setActivePluginRegistry(defaultRegistry);
mocks.appendAssistantMessageToSessionTranscript.mockClear();
hookMocks.runner.hasHooks.mockClear();

View File

@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
resolveSessionAgentId: vi.fn(() => "agent-from-key"),
@ -18,6 +18,7 @@ const mocks = vi.hoisted(() => ({
type SessionMaintenanceWarningModule = typeof import("./session-maintenance-warning.js");
let deliverSessionMaintenanceWarning: SessionMaintenanceWarningModule["deliverSessionMaintenanceWarning"];
let resetSessionMaintenanceWarningForTests: SessionMaintenanceWarningModule["__testing"]["resetSessionMaintenanceWarningForTests"];
function createParams(
overrides: Partial<Parameters<typeof deliverSessionMaintenanceWarning>[0]> = {},
@ -43,18 +44,8 @@ describe("deliverSessionMaintenanceWarning", () => {
let prevVitest: string | undefined;
let prevNodeEnv: string | undefined;
beforeEach(async () => {
prevVitest = process.env.VITEST;
prevNodeEnv = process.env.NODE_ENV;
delete process.env.VITEST;
process.env.NODE_ENV = "development";
beforeAll(async () => {
vi.resetModules();
mocks.resolveSessionAgentId.mockClear();
mocks.deliveryContextFromSession.mockClear();
mocks.normalizeMessageChannel.mockClear();
mocks.isDeliverableMessageChannel.mockClear();
mocks.deliverOutboundPayloads.mockClear();
mocks.enqueueSystemEvent.mockClear();
vi.doMock("../agents/agent-scope.js", () => ({
resolveSessionAgentId: mocks.resolveSessionAgentId,
}));
@ -71,7 +62,24 @@ describe("deliverSessionMaintenanceWarning", () => {
vi.doMock("./system-events.js", () => ({
enqueueSystemEvent: mocks.enqueueSystemEvent,
}));
({ deliverSessionMaintenanceWarning } = await import("./session-maintenance-warning.js"));
({
deliverSessionMaintenanceWarning,
__testing: { resetSessionMaintenanceWarningForTests },
} = await import("./session-maintenance-warning.js"));
});
beforeEach(() => {
prevVitest = process.env.VITEST;
prevNodeEnv = process.env.NODE_ENV;
delete process.env.VITEST;
process.env.NODE_ENV = "development";
resetSessionMaintenanceWarningForTests();
mocks.resolveSessionAgentId.mockClear();
mocks.deliveryContextFromSession.mockClear();
mocks.normalizeMessageChannel.mockClear();
mocks.isDeliverableMessageChannel.mockClear();
mocks.deliverOutboundPayloads.mockClear();
mocks.enqueueSystemEvent.mockClear();
});
afterEach(() => {

View File

@ -18,6 +18,15 @@ const warnedContexts = new Map<string, string>();
const log = createSubsystemLogger("session-maintenance-warning");
let deliverRuntimePromise: Promise<typeof import("./outbound/deliver-runtime.js")> | null = null;
function resetSessionMaintenanceWarningForTests() {
warnedContexts.clear();
deliverRuntimePromise = null;
}
export const __testing = {
resetSessionMaintenanceWarningForTests,
} as const;
function loadDeliverRuntime() {
deliverRuntimePromise ??= import("./outbound/deliver-runtime.js");
return deliverRuntimePromise;

View File

@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
type RegistryModule = typeof import("./registry.js");
type RuntimeModule = typeof import("./runtime.js");
@ -22,7 +22,10 @@ let loadPluginManifestRegistryMock: ReturnType<typeof vi.fn>;
let setActivePluginRegistry: RuntimeModule["setActivePluginRegistry"];
let resolvePluginWebSearchProviders: WebSearchProvidersRuntimeModule["resolvePluginWebSearchProviders"];
let resolveRuntimeWebSearchProviders: WebSearchProvidersRuntimeModule["resolveRuntimeWebSearchProviders"];
let resetWebSearchProviderSnapshotCacheForTests: WebSearchProvidersRuntimeModule["__testing"]["resetWebSearchProviderSnapshotCacheForTests"];
let loadOpenClawPluginsMock: ReturnType<typeof vi.fn>;
let loaderModule: typeof import("./loader.js");
let manifestRegistryModule: ManifestRegistryModule;
function buildMockedWebSearchProviders(params?: {
config?: { plugins?: Record<string, unknown> };
@ -73,10 +76,20 @@ function buildMockedWebSearchProviders(params?: {
}
describe("resolvePluginWebSearchProviders", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ createEmptyPluginRegistry } = await import("./registry.js"));
const manifestRegistryModule = await import("./manifest-registry.js");
manifestRegistryModule = await import("./manifest-registry.js");
loaderModule = await import("./loader.js");
({ setActivePluginRegistry } = await import("./runtime.js"));
({
resolvePluginWebSearchProviders,
resolveRuntimeWebSearchProviders,
__testing: { resetWebSearchProviderSnapshotCacheForTests },
} = await import("./web-search-providers.runtime.js"));
});
beforeEach(() => {
resetWebSearchProviderSnapshotCacheForTests();
loadPluginManifestRegistryMock = vi
.spyOn(manifestRegistryModule, "loadPluginManifestRegistry")
.mockReturnValue({
@ -112,7 +125,6 @@ describe("resolvePluginWebSearchProviders", () => {
) => infer R
? R
: never);
const loaderModule = await import("./loader.js");
loadOpenClawPluginsMock = vi
.spyOn(loaderModule, "loadOpenClawPlugins")
.mockImplementation((params) => {
@ -120,9 +132,6 @@ describe("resolvePluginWebSearchProviders", () => {
registry.webSearchProviders = buildMockedWebSearchProviders(params);
return registry;
});
({ setActivePluginRegistry } = await import("./runtime.js"));
({ resolvePluginWebSearchProviders, resolveRuntimeWebSearchProviders } =
await import("./web-search-providers.runtime.js"));
setActivePluginRegistry(createEmptyPluginRegistry());
vi.useRealTimers();
});

View File

@ -17,11 +17,22 @@ type WebSearchProviderSnapshotCacheEntry = {
expiresAt: number;
providers: PluginWebSearchProviderEntry[];
};
const webSearchProviderSnapshotCache = new WeakMap<
let webSearchProviderSnapshotCache = new WeakMap<
OpenClawConfig,
WeakMap<NodeJS.ProcessEnv, Map<string, WebSearchProviderSnapshotCacheEntry>>
>();
function resetWebSearchProviderSnapshotCacheForTests() {
webSearchProviderSnapshotCache = new WeakMap<
OpenClawConfig,
WeakMap<NodeJS.ProcessEnv, Map<string, WebSearchProviderSnapshotCacheEntry>>
>();
}
export const __testing = {
resetWebSearchProviderSnapshotCacheForTests,
} as const;
const DEFAULT_DISCOVERY_CACHE_MS = 1000;
const DEFAULT_MANIFEST_CACHE_MS = 1000;

View File

@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { AuthProfileStore } from "../agents/auth-profiles.js";
import type { OpenClawConfig } from "../config/config.js";
import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
@ -123,7 +123,7 @@ function loadAuthStoreWithProfiles(profiles: AuthProfileStore["profiles"]): Auth
}
describe("secrets runtime snapshot", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ clearConfigCache } = await import("../config/config.js"));
({
@ -132,6 +132,9 @@ describe("secrets runtime snapshot", () => {
getActiveRuntimeWebToolsMetadata,
prepareSecretsRuntimeSnapshot,
} = await import("./runtime.js"));
});
beforeEach(() => {
resolveBundledPluginWebSearchProvidersMock.mockReset();
resolveBundledPluginWebSearchProvidersMock.mockReturnValue(buildTestWebSearchProviders());
resolvePluginWebSearchProvidersMock.mockReset();

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { WindowsAclEntry, WindowsAclSummary } from "./windows-acl.js";
const MOCK_USERNAME = "MockUser";
@ -24,7 +24,7 @@ let parseIcaclsOutput: typeof import("./windows-acl.js").parseIcaclsOutput;
let resolveWindowsUserPrincipal: typeof import("./windows-acl.js").resolveWindowsUserPrincipal;
let summarizeWindowsAcl: typeof import("./windows-acl.js").summarizeWindowsAcl;
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({
createIcaclsResetCommand,
@ -37,6 +37,10 @@ beforeEach(async () => {
} = await import("./windows-acl.js"));
});
beforeEach(() => {
vi.unstubAllEnvs();
});
function aclEntry(params: {
principal: string;
rights?: string[];