test: speed up core runtime suites

This commit is contained in:
Peter Steinberger 2026-03-31 02:12:14 +01:00
parent f7285e0a9e
commit 6b6ddcd2a6
No known key found for this signature in database
93 changed files with 874 additions and 980 deletions

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 { OpenClawConfig } from "../../config/config.js";
const hoisted = vi.hoisted(() => {
@ -20,12 +20,14 @@ vi.mock("../../config/sessions.js", () => ({
let listAcpSessionEntries: typeof import("./session-meta.js").listAcpSessionEntries;
describe("listAcpSessionEntries", () => {
beforeEach(async () => {
vi.resetModules();
vi.clearAllMocks();
beforeAll(async () => {
({ listAcpSessionEntries } = await import("./session-meta.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
it("reads ACP sessions from resolved configured store targets", async () => {
const cfg = {
session: {

View File

@ -1,4 +1,17 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
sendExecApprovalFollowup: vi.fn(),
logWarn: vi.fn(),
}));
vi.mock("./bash-tools.exec-approval-followup.js", () => ({
sendExecApprovalFollowup: mocks.sendExecApprovalFollowup,
}));
vi.mock("../logger.js", () => ({
logWarn: mocks.logWarn,
}));
let sendExecApprovalFollowupResult: typeof import("./bash-tools.exec-host-shared.js").sendExecApprovalFollowupResult;
let maxExecApprovalFollowupFailureLogKeys: typeof import("./bash-tools.exec-host-shared.js").MAX_EXEC_APPROVAL_FOLLOWUP_FAILURE_LOG_KEYS;
@ -6,20 +19,16 @@ let sendExecApprovalFollowup: typeof import("./bash-tools.exec-approval-followup
let logWarn: typeof import("../logger.js").logWarn;
describe("sendExecApprovalFollowupResult", () => {
beforeEach(async () => {
vi.resetModules();
vi.doMock("./bash-tools.exec-approval-followup.js", () => ({
sendExecApprovalFollowup: vi.fn(),
}));
vi.doMock("../logger.js", () => ({
logWarn: vi.fn(),
}));
beforeAll(async () => {
({
sendExecApprovalFollowupResult,
MAX_EXEC_APPROVAL_FOLLOWUP_FAILURE_LOG_KEYS: maxExecApprovalFollowupFailureLogKeys,
} = await import("./bash-tools.exec-host-shared.js"));
({ sendExecApprovalFollowup } = await import("./bash-tools.exec-approval-followup.js"));
({ logWarn } = await import("../logger.js"));
});
beforeEach(() => {
vi.mocked(sendExecApprovalFollowup).mockReset();
vi.mocked(logWarn).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 { mergeMockedModule } from "../test-utils/vitest-module-mocks.js";
const requestHeartbeatNowMock = vi.hoisted(() => vi.fn());
@ -11,7 +11,7 @@ let formatExecFailureReason: typeof import("./bash-tools.exec-runtime.js").forma
let resolveExecTarget: typeof import("./bash-tools.exec-runtime.js").resolveExecTarget;
describe("detectCursorKeyMode", () => {
beforeEach(async () => {
beforeAll(async () => {
({ detectCursorKeyMode } = await import("./bash-tools.exec-runtime.js"));
});
@ -43,7 +43,7 @@ describe("detectCursorKeyMode", () => {
});
describe("resolveExecTarget", () => {
beforeEach(async () => {
beforeAll(async () => {
({ resolveExecTarget } = await import("./bash-tools.exec-runtime.js"));
});

View File

@ -1,6 +1,13 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../test-utils/fetch-mock.js";
let isMinimaxVlmModel: typeof import("./minimax-vlm.js").isMinimaxVlmModel;
let minimaxUnderstandImage: typeof import("./minimax-vlm.js").minimaxUnderstandImage;
beforeAll(async () => {
({ isMinimaxVlmModel, minimaxUnderstandImage } = await import("./minimax-vlm.js"));
});
describe("minimaxUnderstandImage apiKey normalization", () => {
const priorFetch = global.fetch;
const apiResponse = JSON.stringify({
@ -25,7 +32,6 @@ describe("minimaxUnderstandImage apiKey normalization", () => {
});
global.fetch = withFetchPreconnect(fetchSpy);
const { minimaxUnderstandImage } = await import("./minimax-vlm.js");
const text = await minimaxUnderstandImage({
apiKey,
prompt: "hi",
@ -48,8 +54,6 @@ describe("minimaxUnderstandImage apiKey normalization", () => {
describe("isMinimaxVlmModel", () => {
it("only matches the canonical MiniMax VLM model id", async () => {
const { isMinimaxVlmModel } = await import("./minimax-vlm.js");
expect(isMinimaxVlmModel("minimax", "MiniMax-VL-01")).toBe(true);
expect(isMinimaxVlmModel("minimax-portal", "MiniMax-VL-01")).toBe(true);
expect(isMinimaxVlmModel("minimax-portal", "custom-vision")).toBe(false);

View File

@ -1,9 +1,15 @@
import { describe, expect, it } from "vitest";
import { beforeAll, describe, expect, it } from "vitest";
let normalizeProviderSpecificConfig: typeof import("./models-config.providers.policy.js").normalizeProviderSpecificConfig;
let resolveProviderConfigApiKeyResolver: typeof import("./models-config.providers.policy.js").resolveProviderConfigApiKeyResolver;
beforeAll(async () => {
({ normalizeProviderSpecificConfig, resolveProviderConfigApiKeyResolver } =
await import("./models-config.providers.policy.js"));
});
describe("models-config.providers.policy", () => {
it("resolves config apiKey markers through provider plugin hooks", async () => {
const { resolveProviderConfigApiKeyResolver } =
await import("./models-config.providers.policy.js");
const env = {
AWS_PROFILE: "default",
} as NodeJS.ProcessEnv;
@ -14,8 +20,6 @@ describe("models-config.providers.policy", () => {
});
it("resolves anthropic-vertex ADC markers through provider plugin hooks", async () => {
const { resolveProviderConfigApiKeyResolver } =
await import("./models-config.providers.policy.js");
const resolver = resolveProviderConfigApiKeyResolver("anthropic-vertex");
expect(resolver).toBeTypeOf("function");
@ -27,8 +31,6 @@ describe("models-config.providers.policy", () => {
});
it("normalizes Google provider config through provider plugin hooks", async () => {
const { normalizeProviderSpecificConfig } = await import("./models-config.providers.policy.js");
expect(
normalizeProviderSpecificConfig("google", {
api: "google-generative-ai",

View File

@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import type { RuntimeWebFetchFirecrawlMetadata } from "../secrets/runtime-web-tools.types.js";
import type { RuntimeWebSearchMetadata } from "../secrets/runtime-web-tools.types.js";
@ -138,8 +138,7 @@ async function prepareAndActivate(params: { config: OpenClawConfig; env?: NodeJS
describe("openclaw tools runtime web metadata wiring", () => {
const priorFetch = global.fetch;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
secretsRuntime = await import("../secrets/runtime.js");
({ createWebFetchTool, createWebSearchTool } = await import("./tools/web-tools.js"));
});

View File

@ -12,6 +12,11 @@ import {
import { subscribeEmbeddedPiSession } from "./pi-embedded-subscribe.js";
describe("subscribeEmbeddedPiSession", () => {
async function flushBlockReplyCallbacks(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
}
function createAgentEventHarness(options?: { runId?: string; sessionKey?: string }) {
const { session, emit } = createStubSessionHarness();
const onAgentEvent = vi.fn();
@ -132,10 +137,9 @@ describe("subscribeEmbeddedPiSession", () => {
} as AssistantMessage;
emit({ type: "message_end", message: assistantMessage });
await flushBlockReplyCallbacks();
await vi.waitFor(() => {
expect(onBlockReply).toHaveBeenCalledTimes(1);
});
expect(onBlockReply).toHaveBeenCalledTimes(1);
expect(onBlockReply.mock.calls[0][0].text).toBe("Final answer");
const streamTexts = onReasoningStream.mock.calls
@ -176,10 +180,9 @@ describe("subscribeEmbeddedPiSession", () => {
message: { role: "assistant" },
assistantMessageEvent: { type: "text_end" },
});
await flushBlockReplyCallbacks();
await vi.waitFor(() => {
expect(onBlockReply.mock.calls.length).toBeGreaterThan(0);
});
expect(onBlockReply.mock.calls.length).toBeGreaterThan(0);
const payloadTexts = onBlockReply.mock.calls
.map((call) => call[0]?.text)
.filter((value): value is string => typeof value === "string");

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const resolveProviderCapabilitiesWithPluginMock = vi.fn((params: { provider: string }) => {
switch (params.provider) {
@ -65,24 +65,22 @@ let shouldSanitizeGeminiThoughtSignaturesForModel: typeof import("./provider-cap
let supportsOpenAiCompatTurnValidation: typeof import("./provider-capabilities.js").supportsOpenAiCompatTurnValidation;
let usesMoonshotThinkingPayloadCompat: typeof import("./provider-capabilities.js").usesMoonshotThinkingPayloadCompat;
async function loadFreshProviderCapabilitiesModuleForTest() {
vi.resetModules();
({
isAnthropicProviderFamily,
isOpenAiProviderFamily,
requiresOpenAiCompatibleAnthropicToolPayload,
resolveProviderCapabilities,
resolveTranscriptToolCallIdMode,
shouldDropThinkingBlocksForModel,
shouldSanitizeGeminiThoughtSignaturesForModel,
supportsOpenAiCompatTurnValidation,
usesMoonshotThinkingPayloadCompat,
} = await import("./provider-capabilities.js"));
}
describe("resolveProviderCapabilities", () => {
beforeEach(async () => {
await loadFreshProviderCapabilitiesModuleForTest();
beforeAll(async () => {
({
isAnthropicProviderFamily,
isOpenAiProviderFamily,
requiresOpenAiCompatibleAnthropicToolPayload,
resolveProviderCapabilities,
resolveTranscriptToolCallIdMode,
shouldDropThinkingBlocksForModel,
shouldSanitizeGeminiThoughtSignaturesForModel,
supportsOpenAiCompatTurnValidation,
usesMoonshotThinkingPayloadCompat,
} = await import("./provider-capabilities.js"));
});
beforeEach(() => {
resolveProviderCapabilitiesWithPluginMock.mockClear();
});

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const hoisted = vi.hoisted(() => ({
resolveRuntimePluginRegistry: vi.fn(),
@ -9,14 +9,18 @@ vi.mock("../plugins/loader.js", () => ({
}));
describe("ensureRuntimePluginsLoaded", () => {
let ensureRuntimePluginsLoaded: typeof import("./runtime-plugins.js").ensureRuntimePluginsLoaded;
beforeAll(async () => {
({ ensureRuntimePluginsLoaded } = await import("./runtime-plugins.js"));
});
beforeEach(() => {
hoisted.resolveRuntimePluginRegistry.mockReset();
hoisted.resolveRuntimePluginRegistry.mockReturnValue(undefined);
vi.resetModules();
});
it("does not reactivate plugins when a process already has an active registry", async () => {
const { ensureRuntimePluginsLoaded } = await import("./runtime-plugins.js");
hoisted.resolveRuntimePluginRegistry.mockReturnValue({});
ensureRuntimePluginsLoaded({
@ -29,8 +33,6 @@ describe("ensureRuntimePluginsLoaded", () => {
});
it("resolves runtime plugins through the shared runtime helper", async () => {
const { ensureRuntimePluginsLoaded } = await import("./runtime-plugins.js");
ensureRuntimePluginsLoaded({
config: {} as never,
workspaceDir: "/tmp/workspace",

View File

@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
const FAKE_STARTTIME = 12345;
let __testing: typeof import("./session-write-lock.js").__testing;
@ -10,25 +10,14 @@ let cleanStaleLockFiles: typeof import("./session-write-lock.js").cleanStaleLock
let resetSessionWriteLockStateForTest: typeof import("./session-write-lock.js").resetSessionWriteLockStateForTest;
let resolveSessionLockMaxHoldFromTimeout: typeof import("./session-write-lock.js").resolveSessionLockMaxHoldFromTimeout;
async function loadFreshSessionWriteLockModuleForTest() {
vi.resetModules();
// Mock getProcessStartTime so PID-recycling detection works on non-Linux
// (macOS, CI runners). isPidAlive is left unmocked.
vi.doMock("../shared/pid-alive.js", async (importOriginal) => {
const original = await importOriginal<typeof import("../shared/pid-alive.js")>();
return {
...original,
getProcessStartTime: (pid: number) => (pid === process.pid ? FAKE_STARTTIME : null),
};
});
({
__testing,
acquireSessionWriteLock,
cleanStaleLockFiles,
resetSessionWriteLockStateForTest,
resolveSessionLockMaxHoldFromTimeout,
} = await import("./session-write-lock.js"));
}
vi.mock("../shared/pid-alive.js", async (importOriginal) => {
const original = await importOriginal<typeof import("../shared/pid-alive.js")>();
return {
...original,
// Keep liveness checks real; only pin process start time for PID recycle coverage.
getProcessStartTime: (pid: number) => (pid === process.pid ? FAKE_STARTTIME : null),
};
});
async function expectLockRemovedOnlyAfterFinalRelease(params: {
lockPath: string;
@ -104,8 +93,14 @@ async function expectActiveInProcessLockIsNotReclaimed(params?: {
}
describe("acquireSessionWriteLock", () => {
beforeEach(async () => {
await loadFreshSessionWriteLockModuleForTest();
beforeAll(async () => {
({
__testing,
acquireSessionWriteLock,
cleanStaleLockFiles,
resetSessionWriteLockStateForTest,
resolveSessionLockMaxHoldFromTimeout,
} = await import("./session-write-lock.js"));
});
afterEach(() => {

View File

@ -1,33 +1,36 @@
import type { Model } from "@mariozechner/pi-ai";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
const createAnthropicVertexStreamFnForModel = vi.fn();
const ensureCustomApiRegistered = vi.fn();
const resolveProviderStreamFn = vi.fn();
vi.mock("./anthropic-vertex-stream.js", () => ({
createAnthropicVertexStreamFnForModel,
}));
vi.mock("./custom-api-registry.js", () => ({
ensureCustomApiRegistered,
}));
vi.mock("../plugins/provider-runtime.js", () => ({
resolveProviderStreamFn,
}));
let prepareModelForSimpleCompletion: typeof import("./simple-completion-transport.js").prepareModelForSimpleCompletion;
describe("prepareModelForSimpleCompletion", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ prepareModelForSimpleCompletion } = await import("./simple-completion-transport.js"));
});
beforeEach(() => {
createAnthropicVertexStreamFnForModel.mockReset();
ensureCustomApiRegistered.mockReset();
resolveProviderStreamFn.mockReset();
createAnthropicVertexStreamFnForModel.mockReturnValue("vertex-stream");
resolveProviderStreamFn.mockReturnValue("ollama-stream");
vi.doMock("./anthropic-vertex-stream.js", () => ({
createAnthropicVertexStreamFnForModel,
}));
vi.doMock("./custom-api-registry.js", () => ({
ensureCustomApiRegistered,
}));
vi.doMock("../plugins/provider-runtime.js", () => ({
resolveProviderStreamFn,
}));
({ prepareModelForSimpleCompletion } = await import("./simple-completion-transport.js"));
});
it("registers the configured Ollama transport and keeps the original api", () => {

View File

@ -1,6 +1,6 @@
import fs from "node:fs/promises";
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 type { OpenClawConfig } from "../../config/config.js";
import type { PluginManifestRegistry } from "../../plugins/manifest-registry.js";
import { createTrackedTempDirs } from "../../test-utils/tracked-temp-dirs.js";
@ -15,14 +15,6 @@ vi.mock("../../plugins/manifest-registry.js", () => ({
let resolvePluginSkillDirs: typeof import("./plugin-skills.js").resolvePluginSkillDirs;
async function loadFreshPluginSkillsModuleForTest() {
vi.resetModules();
vi.doMock("../../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry: (...args: unknown[]) => hoisted.loadPluginManifestRegistry(...args),
}));
({ resolvePluginSkillDirs } = await import("./plugin-skills.js"));
}
const tempDirs = createTrackedTempDirs();
function buildRegistry(params: { acpxRoot: string; helperRoot: string }): PluginManifestRegistry {
@ -109,8 +101,12 @@ afterEach(async () => {
});
describe("resolvePluginSkillDirs", () => {
beforeEach(async () => {
await loadFreshPluginSkillsModuleForTest();
beforeAll(async () => {
({ resolvePluginSkillDirs } = await import("./plugin-skills.js"));
});
beforeEach(() => {
hoisted.loadPluginManifestRegistry.mockReset();
});
it.each([

View File

@ -1,6 +1,6 @@
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const watchMock = vi.fn(() => ({
on: vi.fn(),
@ -9,18 +9,17 @@ const watchMock = vi.fn(() => ({
let refreshModule: typeof import("./refresh.js");
async function loadFreshRefreshModuleForTest() {
vi.resetModules();
vi.doMock("chokidar", () => ({
default: { watch: watchMock },
}));
refreshModule = await import("./refresh.js");
}
vi.mock("chokidar", () => ({
default: { watch: watchMock },
}));
describe("ensureSkillsWatcher", () => {
beforeEach(async () => {
beforeAll(async () => {
refreshModule = await import("./refresh.js");
});
beforeEach(() => {
watchMock.mockClear();
await loadFreshRefreshModuleForTest();
});
afterEach(async () => {

View File

@ -1,4 +1,4 @@
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const callGatewayMock = vi.fn();
const configState = vi.hoisted(() => ({
@ -15,28 +15,19 @@ vi.mock("../../gateway/call.js", () => ({
let callGatewayTool: typeof import("./gateway.js").callGatewayTool;
let resolveGatewayOptions: typeof import("./gateway.js").resolveGatewayOptions;
async function loadFreshGatewayToolModuleForTest() {
vi.resetModules();
vi.doMock("../../config/config.js", () => ({
loadConfig: () => configState.value,
resolveGatewayPort: () => 18789,
}));
vi.doMock("../../gateway/call.js", () => ({
callGateway: (...args: unknown[]) => callGatewayMock(...args),
}));
({ callGatewayTool, resolveGatewayOptions } = await import("./gateway.js"));
}
describe("gateway tool defaults", () => {
const envSnapshot = {
openclaw: process.env.OPENCLAW_GATEWAY_TOKEN,
};
beforeEach(async () => {
beforeAll(async () => {
({ callGatewayTool, resolveGatewayOptions } = await import("./gateway.js"));
});
beforeEach(() => {
callGatewayMock.mockClear();
configState.value = {};
delete process.env.OPENCLAW_GATEWAY_TOKEN;
await loadFreshGatewayToolModuleForTest();
});
afterAll(() => {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const gatewayMocks = vi.hoisted(() => ({
callGatewayTool: vi.fn(),
@ -21,12 +21,14 @@ function node({ nodeId, ...overrides }: Partial<NodeListNode> & { nodeId: string
};
}
beforeEach(async () => {
vi.resetModules();
gatewayMocks.callGatewayTool.mockReset();
beforeAll(async () => {
({ listNodes, resolveNodeIdFromList } = await import("./nodes-utils.js"));
});
beforeEach(() => {
gatewayMocks.callGatewayTool.mockReset();
});
describe("resolveNodeIdFromList defaults", () => {
it("falls back to most recently connected node when multiple non-Mac candidates exist", () => {
const nodes: NodeListNode[] = [

View File

@ -1,12 +1,59 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
gatewayCall: vi.fn(),
createAgentToAgentPolicy: vi.fn(() => ({})),
createSessionVisibilityGuard: vi.fn(async () => ({
check: () => ({ allowed: true }),
})),
resolveEffectiveSessionToolsVisibility: vi.fn(() => "all"),
resolveSandboxedSessionToolContext: vi.fn(() => ({
mainKey: "main",
alias: "main",
requesterInternalKey: undefined,
restrictToSpawned: false,
})),
}));
vi.mock("../../gateway/call.js", () => ({
callGateway: (opts: unknown) => mocks.gatewayCall(opts),
}));
vi.mock("./sessions-helpers.js", async (importActual) => {
const actual = await importActual<typeof import("./sessions-helpers.js")>();
return {
...actual,
createAgentToAgentPolicy: () => mocks.createAgentToAgentPolicy(),
createSessionVisibilityGuard: async () => await mocks.createSessionVisibilityGuard(),
resolveEffectiveSessionToolsVisibility: () => mocks.resolveEffectiveSessionToolsVisibility(),
resolveSandboxedSessionToolContext: () => mocks.resolveSandboxedSessionToolContext(),
};
});
describe("sessions-list-tool", () => {
let createSessionsListTool: typeof import("./sessions-list-tool.js").createSessionsListTool;
beforeAll(async () => {
({ createSessionsListTool } = await import("./sessions-list-tool.js"));
});
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
mocks.createAgentToAgentPolicy.mockReturnValue({});
mocks.createSessionVisibilityGuard.mockResolvedValue({
check: () => ({ allowed: true }),
});
mocks.resolveEffectiveSessionToolsVisibility.mockReturnValue("all");
mocks.resolveSandboxedSessionToolContext.mockReturnValue({
mainKey: "main",
alias: "main",
requesterInternalKey: undefined,
restrictToSpawned: false,
});
});
it("keeps deliveryContext.threadId in sessions_list results", async () => {
const gatewayCallMock = vi.fn(async (opts: unknown) => {
mocks.gatewayCall.mockImplementation(async (opts: unknown) => {
const request = opts as { method?: string };
if (request.method === "sessions.list") {
return {
@ -39,30 +86,6 @@ describe("sessions-list-tool", () => {
}
return {};
});
vi.doMock("../../gateway/call.js", () => ({
callGateway: gatewayCallMock,
}));
vi.doMock("./sessions-helpers.js", async () => {
const actual =
await vi.importActual<typeof import("./sessions-helpers.js")>("./sessions-helpers.js");
return {
...actual,
createAgentToAgentPolicy: () => ({}),
createSessionVisibilityGuard: async () => ({
check: () => ({ allowed: true }),
}),
resolveEffectiveSessionToolsVisibility: () => "all",
resolveSandboxedSessionToolContext: () => ({
mainKey: "main",
alias: "main",
requesterInternalKey: undefined,
restrictToSpawned: false,
}),
};
});
const { createSessionsListTool } = await import("./sessions-list-tool.js");
const tool = createSessionsListTool({ config: {} as never });
const result = await tool.execute("call-1", {});
@ -92,7 +115,7 @@ describe("sessions-list-tool", () => {
});
it("keeps numeric deliveryContext.threadId in sessions_list results", async () => {
const gatewayCallMock = vi.fn(async (opts: unknown) => {
mocks.gatewayCall.mockImplementation(async (opts: unknown) => {
const request = opts as { method?: string };
if (request.method === "sessions.list") {
return {
@ -114,30 +137,6 @@ describe("sessions-list-tool", () => {
}
return {};
});
vi.doMock("../../gateway/call.js", () => ({
callGateway: gatewayCallMock,
}));
vi.doMock("./sessions-helpers.js", async () => {
const actual =
await vi.importActual<typeof import("./sessions-helpers.js")>("./sessions-helpers.js");
return {
...actual,
createAgentToAgentPolicy: () => ({}),
createSessionVisibilityGuard: async () => ({
check: () => ({ allowed: true }),
}),
resolveEffectiveSessionToolsVisibility: () => "all",
resolveSandboxedSessionToolContext: () => ({
mainKey: "main",
alias: "main",
requesterInternalKey: undefined,
restrictToSpawned: false,
}),
};
});
const { createSessionsListTool } = await import("./sessions-list-tool.js");
const tool = createSessionsListTool({ config: {} as never });
const result = await tool.execute("call-2", {});
@ -161,7 +160,7 @@ describe("sessions-list-tool", () => {
});
it("keeps live session setting metadata in sessions_list results", async () => {
const gatewayCallMock = vi.fn(async (opts: unknown) => {
mocks.gatewayCall.mockImplementation(async (opts: unknown) => {
const request = opts as { method?: string };
if (request.method === "sessions.list") {
return {
@ -183,30 +182,6 @@ describe("sessions-list-tool", () => {
}
return {};
});
vi.doMock("../../gateway/call.js", () => ({
callGateway: gatewayCallMock,
}));
vi.doMock("./sessions-helpers.js", async () => {
const actual =
await vi.importActual<typeof import("./sessions-helpers.js")>("./sessions-helpers.js");
return {
...actual,
createAgentToAgentPolicy: () => ({}),
createSessionVisibilityGuard: async () => ({
check: () => ({ allowed: true }),
}),
resolveEffectiveSessionToolsVisibility: () => "all",
resolveSandboxedSessionToolContext: () => ({
mainKey: "main",
alias: "main",
requesterInternalKey: undefined,
restrictToSpawned: false,
}),
};
});
const { createSessionsListTool } = await import("./sessions-list-tool.js");
const tool = createSessionsListTool({ config: {} as never });
const result = await tool.execute("call-3", {});

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 { OpenClawConfig } from "../../config/config.js";
const callGatewayMock = vi.fn();
vi.mock("../../gateway/call.js", () => ({
@ -14,11 +14,7 @@ let resolveSessionReference: typeof import("./sessions-resolution.js").resolveSe
let shouldVerifyRequesterSpawnedSessionVisibility: typeof import("./sessions-resolution.js").shouldVerifyRequesterSpawnedSessionVisibility;
let shouldResolveSessionIdInput: typeof import("./sessions-resolution.js").shouldResolveSessionIdInput;
async function loadFreshSessionsResolutionModuleForTest() {
vi.resetModules();
vi.doMock("../../gateway/call.js", () => ({
callGateway: (opts: unknown) => callGatewayMock(opts),
}));
beforeAll(async () => {
({
isResolvedSessionVisibleToRequester,
looksLikeSessionId,
@ -30,11 +26,10 @@ async function loadFreshSessionsResolutionModuleForTest() {
shouldVerifyRequesterSpawnedSessionVisibility,
shouldResolveSessionIdInput,
} = await import("./sessions-resolution.js"));
}
});
beforeEach(async () => {
beforeEach(() => {
callGatewayMock.mockReset();
await loadFreshSessionsResolutionModuleForTest();
});
describe("resolveMainSessionAlias", () => {

View File

@ -1,18 +1,17 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SILENT_REPLY_TOKEN } from "../../auto-reply/tokens.js";
import * as ttsRuntime from "../../tts/tts.js";
import { createTtsTool } from "./tts-tool.js";
let textToSpeechSpy: ReturnType<typeof vi.spyOn>;
describe("createTtsTool", () => {
beforeEach(async () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.resetModules();
const ttsRuntime = await import("../../tts/tts.js");
textToSpeechSpy = vi.spyOn(ttsRuntime, "textToSpeech");
});
it("uses SILENT_REPLY_TOKEN in guidance text", async () => {
const { createTtsTool } = await import("./tts-tool.js");
it("uses SILENT_REPLY_TOKEN in guidance text", () => {
const tool = createTtsTool();
expect(tool.description).toContain(SILENT_REPLY_TOKEN);
@ -26,7 +25,6 @@ describe("createTtsTool", () => {
voiceCompatible: true,
});
const { createTtsTool } = await import("./tts-tool.js");
const tool = createTtsTool();
const result = await tool.execute("call-1", { text: "hello" });

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../plugins/provider-runtime.js", () => ({
resolveProviderCapabilitiesWithPlugin: vi.fn(({ provider }: { provider?: string }) => {
@ -29,14 +29,13 @@ vi.mock("../plugins/provider-runtime.js", () => ({
let resolveTranscriptPolicy: typeof import("./transcript-policy.js").resolveTranscriptPolicy;
async function loadFreshTranscriptPolicyModuleForTest() {
vi.resetModules();
({ resolveTranscriptPolicy } = await import("./transcript-policy.js"));
}
describe("resolveTranscriptPolicy", () => {
beforeEach(async () => {
await loadFreshTranscriptPolicyModuleForTest();
beforeAll(async () => {
({ resolveTranscriptPolicy } = await import("./transcript-policy.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
it("enables sanitizeToolCallIds for Anthropic provider", () => {

View File

@ -40,7 +40,7 @@ describe("emitResetCommandHooks", () => {
workspaceDir: "/tmp/openclaw-workspace",
});
await vi.waitFor(() => expect(hookRunnerMocks.runBeforeReset).toHaveBeenCalledTimes(1));
expect(hookRunnerMocks.runBeforeReset).toHaveBeenCalledTimes(1);
const [, ctx] = hookRunnerMocks.runBeforeReset.mock.calls[0] ?? [];
return ctx;
}

View File

@ -65,7 +65,7 @@ describe("session hook context wiring", () => {
commandAuthorized: true,
});
await vi.waitFor(() => expect(hookRunnerMocks.runSessionStart).toHaveBeenCalledTimes(1));
expect(hookRunnerMocks.runSessionStart).toHaveBeenCalledTimes(1);
const [event, context] = hookRunnerMocks.runSessionStart.mock.calls[0] ?? [];
expect(event).toMatchObject({ sessionKey });
expect(context).toMatchObject({ sessionKey, agentId: "main" });
@ -89,8 +89,8 @@ describe("session hook context wiring", () => {
commandAuthorized: true,
});
await vi.waitFor(() => expect(hookRunnerMocks.runSessionEnd).toHaveBeenCalledTimes(1));
await vi.waitFor(() => expect(hookRunnerMocks.runSessionStart).toHaveBeenCalledTimes(1));
expect(hookRunnerMocks.runSessionEnd).toHaveBeenCalledTimes(1);
expect(hookRunnerMocks.runSessionStart).toHaveBeenCalledTimes(1);
const [event, context] = hookRunnerMocks.runSessionEnd.mock.calls[0] ?? [];
expect(event).toMatchObject({ sessionKey });
expect(context).toMatchObject({ sessionKey, agentId: "main" });

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 { MsgContext } from "../auto-reply/templating.js";
const recordSessionMetaFromInboundMock = vi.fn((_args?: unknown) => Promise.resolve(undefined));
@ -21,9 +21,11 @@ describe("recordInboundSession", () => {
OriginatingTo: "demo-channel:1234",
};
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ recordInboundSession } = await import("./session.js"));
});
beforeEach(() => {
recordSessionMetaFromInboundMock.mockClear();
updateLastRouteMock.mockClear();
});

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";
import { buildWebSearchProviderConfig } from "./test-helpers.js";
vi.mock("../runtime.js", () => ({
@ -77,8 +77,7 @@ vi.mock("../plugins/web-search-providers.js", () => {
let validateConfigObjectWithPlugins: typeof import("./config.js").validateConfigObjectWithPlugins;
let resolveSearchProvider: typeof import("../agents/tools/web-search.js").__testing.resolveSearchProvider;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ validateConfigObjectWithPlugins } = await import("./config.js"));
({
__testing: { resolveSearchProvider },

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
createConfigIO: vi.fn().mockReturnValue({
@ -13,11 +13,14 @@ vi.mock("./io.js", () => ({
let formatConfigPath: typeof import("./logging.js").formatConfigPath;
let logConfigUpdated: typeof import("./logging.js").logConfigUpdated;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ formatConfigPath, logConfigUpdated } = await import("./logging.js"));
});
beforeEach(() => {
mocks.createConfigIO.mockClear();
});
describe("config logging", () => {
it("formats the live config path when no explicit path is provided", () => {
expect(formatConfigPath()).toBe("/tmp/openclaw-dev/openclaw.json");

View File

@ -1,10 +1,13 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ConfigFileSnapshot, OpenClawConfig } from "./types.js";
const mockLoadConfig = vi.hoisted(() => vi.fn<() => OpenClawConfig>());
const mockReadConfigFileSnapshot = vi.hoisted(() => vi.fn<() => Promise<ConfigFileSnapshot>>());
const mockLoadPluginManifestRegistry = vi.hoisted(() => vi.fn());
let readBestEffortRuntimeConfigSchema: typeof import("./runtime-schema.js").readBestEffortRuntimeConfigSchema;
let loadGatewayRuntimeConfigSchema: typeof import("./runtime-schema.js").loadGatewayRuntimeConfigSchema;
vi.mock("./config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./config.js")>();
return {
@ -127,7 +130,6 @@ function makeManifestRegistry() {
}
async function readSchemaNodes() {
const { readBestEffortRuntimeConfigSchema } = await import("./runtime-schema.js");
const result = await readBestEffortRuntimeConfigSchema();
const schema = result.schema as { properties?: Record<string, unknown> };
const channelsNode = schema.properties?.channels as Record<string, unknown> | undefined;
@ -139,6 +141,11 @@ async function readSchemaNodes() {
return { channelProps, entryProps };
}
beforeAll(async () => {
({ readBestEffortRuntimeConfigSchema, loadGatewayRuntimeConfigSchema } =
await import("./runtime-schema.js"));
});
describe("readBestEffortRuntimeConfigSchema", () => {
beforeEach(() => {
vi.clearAllMocks();
@ -192,7 +199,6 @@ describe("loadGatewayRuntimeConfigSchema", () => {
});
it("uses manifest metadata instead of booting plugin runtime", async () => {
const { loadGatewayRuntimeConfigSchema } = await import("./runtime-schema.js");
const result = loadGatewayRuntimeConfigSchema();
const schema = result.schema as { properties?: Record<string, unknown> };
const channelsNode = schema.properties?.channels as Record<string, unknown> | undefined;

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 { SessionEntry } from "./types.js";
const storeState = vi.hoisted(() => ({
@ -26,12 +26,14 @@ const buildEntry = (deliveryContext: SessionEntry["deliveryContext"]): SessionEn
deliveryContext,
});
beforeEach(async () => {
vi.resetModules();
storeState.store = {};
beforeAll(async () => {
({ extractDeliveryInfo, parseSessionThreadInfo } = await import("./delivery-info.js"));
});
beforeEach(() => {
storeState.store = {};
});
describe("extractDeliveryInfo", () => {
it("parses base session and thread/topic ids", () => {
expect(parseSessionThreadInfo("agent:main:telegram:group:1:topic:55")).toEqual({

View File

@ -1,11 +1,19 @@
import { describe, expect, it, vi } from "vitest";
import { beforeAll, describe, expect, it, vi } from "vitest";
const mockLoadPluginManifestRegistry = vi.hoisted(() => vi.fn());
let validateConfigObjectWithPlugins: typeof import("./validation.js").validateConfigObjectWithPlugins;
let validateConfigObjectRawWithPlugins: typeof import("./validation.js").validateConfigObjectRawWithPlugins;
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry: (...args: unknown[]) => mockLoadPluginManifestRegistry(...args),
}));
beforeAll(async () => {
({ validateConfigObjectWithPlugins, validateConfigObjectRawWithPlugins } =
await import("./validation.js"));
});
function setupTelegramSchemaWithDefault() {
mockLoadPluginManifestRegistry.mockReturnValue({
diagnostics: [],
@ -44,7 +52,6 @@ describe("validateConfigObjectWithPlugins channel metadata (applyDefaults: true)
it("applies bundled channel defaults from plugin-owned schema metadata", async () => {
setupTelegramSchemaWithDefault();
const { validateConfigObjectWithPlugins } = await import("./validation.js");
const result = validateConfigObjectWithPlugins({
channels: {
telegram: {},
@ -71,7 +78,6 @@ describe("validateConfigObjectRawWithPlugins channel metadata", () => {
// merge-patched value) instead of validated.config.
setupTelegramSchemaWithDefault();
const { validateConfigObjectRawWithPlugins } = await import("./validation.js");
const result = validateConfigObjectRawWithPlugins({
channels: {
telegram: {},

View File

@ -1556,12 +1556,17 @@ describe("Cron issue regressions", () => {
let now = dueAt;
let activeRuns = 0;
let peakActiveRuns = 0;
const firstStarted = createDeferred<void>();
const firstRun = createDeferred<{ status: "ok"; summary: string }>();
const secondRun = createDeferred<{ status: "ok"; summary: string }>();
const secondStarted = createDeferred<void>();
const bothFinished = createDeferred<void>();
const runIsolatedAgentJob = vi.fn(async (params: { job: { id: string } }) => {
activeRuns += 1;
peakActiveRuns = Math.max(peakActiveRuns, activeRuns);
if (params.job.id === first.id) {
firstStarted.resolve();
}
if (params.job.id === second.id) {
secondStarted.resolve();
}
@ -1583,6 +1588,11 @@ describe("Cron issue regressions", () => {
enqueueSystemEvent: vi.fn(),
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob,
onEvent: (evt) => {
if (evt.action === "finished" && evt.jobId === second.id && evt.status === "ok") {
bothFinished.resolve();
}
},
});
const firstAck = await enqueueRun(state, first.id, "force");
@ -1590,7 +1600,7 @@ describe("Cron issue regressions", () => {
expect(firstAck).toEqual({ ok: true, enqueued: true, runId: expect.any(String) });
expect(secondAck).toEqual({ ok: true, enqueued: true, runId: expect.any(String) });
await vi.waitFor(() => expect(runIsolatedAgentJob).toHaveBeenCalledTimes(1));
await firstStarted.promise;
expect(runIsolatedAgentJob.mock.calls[0]?.[0]).toMatchObject({ job: { id: first.id } });
expect(peakActiveRuns).toBe(1);
@ -1601,11 +1611,10 @@ describe("Cron issue regressions", () => {
expect(peakActiveRuns).toBe(1);
secondRun.resolve({ status: "ok", summary: "second queued run" });
await vi.waitFor(() => {
const jobs = state.store?.jobs ?? [];
expect(jobs.find((job) => job.id === first.id)?.state.lastStatus).toBe("ok");
expect(jobs.find((job) => job.id === second.id)?.state.lastStatus).toBe("ok");
});
await bothFinished.promise;
const jobs = state.store?.jobs ?? [];
expect(jobs.find((job) => job.id === first.id)?.state.lastStatus).toBe("ok");
expect(jobs.find((job) => job.id === second.id)?.state.lastStatus).toBe("ok");
clearCommandLane(CommandLane.Cron);
});
@ -1618,6 +1627,10 @@ describe("Cron issue regressions", () => {
const dueAt = Date.parse("2026-02-06T10:05:03.000Z");
const job = createDueIsolatedJob({ id: "queued-failure", nowMs: dueAt, nextRunAtMs: dueAt });
const log = createNoopLogger();
const errorLogged = createDeferred<void>();
log.error.mockImplementation(() => {
errorLogged.resolve();
});
const badStore = `${makeStorePath().storePath}.dir`;
await fs.mkdir(badStore, { recursive: true });
const state = createRunningCronServiceState({
@ -1630,7 +1643,8 @@ describe("Cron issue regressions", () => {
const result = await enqueueRun(state, job.id, "force");
expect(result).toEqual({ ok: true, enqueued: true, runId: expect.any(String) });
await vi.waitFor(() => expect(log.error).toHaveBeenCalledTimes(1));
await errorLogged.promise;
expect(log.error).toHaveBeenCalledTimes(1);
expect(log.error.mock.calls[0]?.[1]).toBe(
"cron: queued manual run background execution failed",
);

View File

@ -1,21 +1,22 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { beforeAll, describe, expect, it, vi } from "vitest";
afterEach(() => {
vi.resetModules();
vi.doUnmock("./launchd.js");
});
const resolveGatewayLogPathsMock = vi.fn(() => ({
stdoutPath: "C:\\tmp\\openclaw-state\\logs\\gateway.log",
stderrPath: "C:\\tmp\\openclaw-state\\logs\\gateway.err.log",
}));
vi.mock("./launchd.js", () => ({
resolveGatewayLogPaths: resolveGatewayLogPathsMock,
}));
let buildPlatformRuntimeLogHints: typeof import("./runtime-hints.js").buildPlatformRuntimeLogHints;
describe("buildPlatformRuntimeLogHints", () => {
it("strips windows drive prefixes from darwin display paths", async () => {
vi.doMock("./launchd.js", () => ({
resolveGatewayLogPaths: () => ({
stdoutPath: "C:\\tmp\\openclaw-state\\logs\\gateway.log",
stderrPath: "C:\\tmp\\openclaw-state\\logs\\gateway.err.log",
}),
}));
const { buildPlatformRuntimeLogHints } = await import("./runtime-hints.js");
beforeAll(async () => {
({ buildPlatformRuntimeLogHints } = await import("./runtime-hints.js"));
});
it("strips windows drive prefixes from darwin display paths", () => {
expect(
buildPlatformRuntimeLogHints({
platform: "darwin",

View File

@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { execSchtasks } from "./schtasks-exec.js";
const runCommandWithTimeout = vi.hoisted(() => vi.fn());
@ -6,8 +7,6 @@ vi.mock("../process/exec.js", () => ({
runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeout(...args),
}));
const { execSchtasks } = await import("./schtasks-exec.js");
beforeEach(() => {
runCommandWithTimeout.mockReset();
});

View File

@ -12,6 +12,7 @@ import { splitArgsPreservingQuotes } from "./arg-split.js";
import { parseSystemdExecStart } from "./systemd-unit.js";
import {
isNonFatalSystemdInstallProbeError,
isSystemdServiceEnabled,
isSystemdUserServiceAvailable,
parseSystemdShow,
readSystemdServiceExecStart,
@ -71,7 +72,6 @@ function assertMachineUserSystemctlArgs(args: string[], user: string, ...command
}
async function readManagedServiceEnabled(env: NodeJS.ProcessEnv = { HOME: TEST_MANAGED_HOME }) {
const { isSystemdServiceEnabled } = await import("./systemd.js");
vi.spyOn(fs, "access").mockResolvedValue(undefined);
return isSystemdServiceEnabled({ env });
}
@ -180,7 +180,6 @@ describe("isSystemdServiceEnabled", () => {
});
it("returns false without calling systemctl when the managed unit file is missing", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
const err = new Error("missing unit") as NodeJS.ErrnoException;
err.code = "ENOENT";
vi.spyOn(fs, "access").mockRejectedValueOnce(err);
@ -286,7 +285,6 @@ describe("isSystemdServiceEnabled", () => {
});
it("throws when systemctl is-enabled fails for non-state errors", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
vi.spyOn(fs, "access").mockResolvedValue(undefined);
execFileMock
.mockImplementationOnce((_cmd, args, _opts, cb) => {
@ -309,7 +307,6 @@ describe("isSystemdServiceEnabled", () => {
});
it("returns false when systemctl is-enabled exits with code 4 (not-found)", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
vi.spyOn(fs, "access").mockResolvedValue(undefined);
execFileMock.mockImplementationOnce((_cmd, _args, _opts, cb) => {
// On Ubuntu 24.04, `systemctl --user is-enabled <unit>` exits with

View File

@ -98,7 +98,6 @@ describe("entry root version fast path", () => {
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);
@ -112,7 +111,6 @@ describe("entry root version fast path", () => {
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);
@ -126,7 +124,6 @@ describe("entry root version fast path", () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await import("./entry.js");
await vi.waitFor(() => {
expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]);
});
@ -142,7 +139,6 @@ describe("entry root version fast path", () => {
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
await import("./entry.js");
await vi.waitFor(() => {
expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]);
});

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 { OpenClawConfig } from "../config/config.js";
const ensureOpenClawModelsJsonMock = vi.fn<
@ -39,14 +39,21 @@ vi.mock("../agents/pi-embedded-runner/model.js", () => ({
) => resolveModelMock(provider, modelId, agentDir, cfg, options),
}));
let prewarmConfiguredPrimaryModel: typeof import("./server-startup.js").__testing.prewarmConfiguredPrimaryModel;
describe("gateway startup primary model warmup", () => {
beforeAll(async () => {
({
__testing: { prewarmConfiguredPrimaryModel },
} = await import("./server-startup.js"));
});
beforeEach(() => {
ensureOpenClawModelsJsonMock.mockClear();
resolveModelMock.mockClear();
});
it("prewarms an explicit configured primary model", async () => {
const { __testing } = await import("./server-startup.js");
const cfg = {
agents: {
defaults: {
@ -57,7 +64,7 @@ describe("gateway startup primary model warmup", () => {
},
} as OpenClawConfig;
await __testing.prewarmConfiguredPrimaryModel({
await prewarmConfiguredPrimaryModel({
cfg,
log: { warn: vi.fn() },
});
@ -69,9 +76,7 @@ describe("gateway startup primary model warmup", () => {
});
it("skips warmup when no explicit primary model is configured", async () => {
const { __testing } = await import("./server-startup.js");
await __testing.prewarmConfiguredPrimaryModel({
await prewarmConfiguredPrimaryModel({
cfg: {} as OpenClawConfig,
log: { warn: vi.fn() },
});

View File

@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
const { resolveRuntimePluginRegistryMock } = vi.hoisted(() => ({
@ -15,17 +15,16 @@ let getImageGenerationProvider: typeof import("./provider-registry.js").getImage
let listImageGenerationProviders: typeof import("./provider-registry.js").listImageGenerationProviders;
describe("image-generation provider registry", () => {
beforeAll(async () => {
({ getImageGenerationProvider, listImageGenerationProviders } =
await import("./provider-registry.js"));
});
afterEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
});
beforeEach(async () => {
vi.resetModules();
({ getImageGenerationProvider, listImageGenerationProviders } =
await import("./provider-registry.js"));
});
it("does not load plugins when listing without config", () => {
expect(listImageGenerationProviders()).toEqual([]);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();

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";
import type { OpenClawConfig } from "../config/config.js";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js";
@ -24,20 +24,18 @@ function setCompatibleActiveImageGenerationRegistry(
}
describe("image-generation runtime helpers", () => {
beforeAll(async () => {
({ generateImage, listRuntimeImageGenerationProviders } = await import("./runtime.js"));
});
afterEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
resetPluginRuntimeStateForTest();
vi.doUnmock("../plugins/loader.js");
});
beforeEach(async () => {
vi.resetModules();
beforeEach(() => {
resetPluginRuntimeStateForTest();
vi.doMock("../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: resolveRuntimePluginRegistryMock,
}));
({ generateImage, listRuntimeImageGenerationProviders } = await import("./runtime.js"));
});
it("generates images through the active image-generation registry", async () => {

View File

@ -22,6 +22,7 @@ beforeEach(async () => {
vi.resetModules();
({ isTruthyEnvValue, logAcceptedEnvOption, normalizeEnv, normalizeZaiEnv } =
await import("./env.js"));
loggerMocks.info.mockClear();
});
describe("normalizeZaiEnv", () => {

View File

@ -29,6 +29,11 @@ afterEach(() => {
const emptyRegistry = createTestRegistry([]);
async function flushPendingDelivery(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
}
function isDiscordExecApprovalClientEnabledForTest(params: {
cfg: OpenClawConfig;
accountId?: string | null;
@ -368,9 +373,8 @@ describe("exec approval forwarder", () => {
const { deliver, forwarder } = createForwarder({ cfg: TARGETS_CFG });
await expect(forwarder.handleRequested(baseRequest)).resolves.toBe(true);
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
expect(beforeDeliverPayload).toHaveBeenCalledWith(
expect.objectContaining({
hint: { kind: "approval-pending", approvalKind: "exec" },

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const loadConfigMock = vi.hoisted(() => vi.fn());
const getChannelPluginMock = vi.hoisted(() => vi.fn());
@ -6,47 +6,48 @@ const listChannelPluginsMock = vi.hoisted(() => vi.fn());
const isDeliverableMessageChannelMock = vi.hoisted(() => vi.fn());
const normalizeMessageChannelMock = vi.hoisted(() => vi.fn());
vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
};
});
vi.mock("../channels/plugins/index.js", () => ({
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
listChannelPlugins: (...args: unknown[]) => listChannelPluginsMock(...args),
}));
vi.mock("../utils/message-channel.js", () => ({
INTERNAL_MESSAGE_CHANNEL: "web",
isDeliverableMessageChannel: (...args: unknown[]) => isDeliverableMessageChannelMock(...args),
normalizeMessageChannel: (...args: unknown[]) => normalizeMessageChannelMock(...args),
}));
type ExecApprovalSurfaceModule = typeof import("./exec-approval-surface.js");
let hasConfiguredExecApprovalDmRoute: ExecApprovalSurfaceModule["hasConfiguredExecApprovalDmRoute"];
let resolveExecApprovalInitiatingSurfaceState: ExecApprovalSurfaceModule["resolveExecApprovalInitiatingSurfaceState"];
async function loadExecApprovalSurfaceModule() {
vi.resetModules();
loadConfigMock.mockReset();
getChannelPluginMock.mockReset();
listChannelPluginsMock.mockReset();
isDeliverableMessageChannelMock.mockReset();
normalizeMessageChannelMock.mockReset();
normalizeMessageChannelMock.mockImplementation((value?: string | null) =>
typeof value === "string" ? value.trim().toLowerCase() : undefined,
);
isDeliverableMessageChannelMock.mockImplementation(
(value?: string) => value === "slack" || value === "discord" || value === "telegram",
);
vi.doMock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
};
});
vi.doMock("../channels/plugins/index.js", () => ({
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
listChannelPlugins: (...args: unknown[]) => listChannelPluginsMock(...args),
}));
vi.doMock("../utils/message-channel.js", () => ({
INTERNAL_MESSAGE_CHANNEL: "web",
isDeliverableMessageChannel: (...args: unknown[]) => isDeliverableMessageChannelMock(...args),
normalizeMessageChannel: (...args: unknown[]) => normalizeMessageChannelMock(...args),
}));
({ hasConfiguredExecApprovalDmRoute, resolveExecApprovalInitiatingSurfaceState } =
await import("./exec-approval-surface.js"));
}
describe("resolveExecApprovalInitiatingSurfaceState", () => {
beforeEach(async () => {
await loadExecApprovalSurfaceModule();
beforeAll(async () => {
({ hasConfiguredExecApprovalDmRoute, resolveExecApprovalInitiatingSurfaceState } =
await import("./exec-approval-surface.js"));
});
beforeEach(() => {
loadConfigMock.mockReset();
getChannelPluginMock.mockReset();
listChannelPluginsMock.mockReset();
isDeliverableMessageChannelMock.mockReset();
normalizeMessageChannelMock.mockReset();
normalizeMessageChannelMock.mockImplementation((value?: string | null) =>
typeof value === "string" ? value.trim().toLowerCase() : undefined,
);
isDeliverableMessageChannelMock.mockImplementation(
(value?: string) => value === "slack" || value === "discord" || value === "telegram",
);
});
it.each([
@ -163,8 +164,18 @@ describe("resolveExecApprovalInitiatingSurfaceState", () => {
});
describe("hasConfiguredExecApprovalDmRoute", () => {
beforeEach(async () => {
await loadExecApprovalSurfaceModule();
beforeEach(() => {
loadConfigMock.mockReset();
getChannelPluginMock.mockReset();
listChannelPluginsMock.mockReset();
isDeliverableMessageChannelMock.mockReset();
normalizeMessageChannelMock.mockReset();
normalizeMessageChannelMock.mockImplementation((value?: string | null) =>
typeof value === "string" ? value.trim().toLowerCase() : undefined,
);
isDeliverableMessageChannelMock.mockImplementation(
(value?: string) => value === "slack" || value === "discord" || value === "telegram",
);
});
it.each([

View File

@ -1,6 +1,6 @@
import fs from "node:fs";
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 { makeTempDir } from "./exec-approvals-test-helpers.js";
const requestJsonlSocketMock = vi.hoisted(() => vi.fn());
@ -26,8 +26,7 @@ let resolveExecApprovalsSocketPath: ExecApprovalsModule["resolveExecApprovalsSoc
const tempDirs: string[] = [];
const originalOpenClawHome = process.env.OPENCLAW_HOME;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({
addAllowlistEntry,
ensureExecApprovals,
@ -39,6 +38,9 @@ beforeEach(async () => {
resolveExecApprovalsPath,
resolveExecApprovalsSocketPath,
} = await import("./exec-approvals.js"));
});
beforeEach(() => {
requestJsonlSocketMock.mockReset();
});

View File

@ -1,4 +1,4 @@
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const PROXY_ENV_KEYS = [
"HTTPS_PROXY",
@ -75,13 +75,15 @@ function restoreProxyEnv(): void {
}
describe("makeProxyFetch", () => {
beforeEach(async () => {
vi.resetModules();
vi.clearAllMocks();
beforeAll(async () => {
({ getProxyUrlFromFetch, makeProxyFetch, PROXY_FETCH_PROXY_URL, resolveProxyFetchFromEnv } =
await import("./proxy-fetch.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
it("uses undici fetch with ProxyAgent dispatcher", async () => {
const proxyUrl = "http://proxy.test:8080";
undiciFetch.mockResolvedValue({ ok: true });
@ -216,5 +218,4 @@ afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});

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";
import { TEST_UNDICI_RUNTIME_DEPS_KEY } from "./undici-runtime.js";
const { agentCtor, envHttpProxyAgentCtor, proxyAgentCtor } = vi.hoisted(() => ({
@ -20,8 +20,11 @@ import type { PinnedHostname } from "./ssrf.js";
let createPinnedDispatcher: typeof import("./ssrf.js").createPinnedDispatcher;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ createPinnedDispatcher } = await import("./ssrf.js"));
});
beforeEach(() => {
agentCtor.mockClear();
envHttpProxyAgentCtor.mockClear();
proxyAgentCtor.mockClear();
@ -30,7 +33,6 @@ beforeEach(async () => {
EnvHttpProxyAgent: envHttpProxyAgentCtor,
ProxyAgent: proxyAgentCtor,
};
({ createPinnedDispatcher } = await import("./ssrf.js"));
});
afterEach(() => {

View File

@ -1,4 +1,4 @@
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const {
Agent,
@ -75,14 +75,16 @@ let ensureGlobalUndiciStreamTimeouts: typeof import("./undici-global-dispatcher.
let resetGlobalUndiciStreamTimeoutsForTests: typeof import("./undici-global-dispatcher.js").resetGlobalUndiciStreamTimeoutsForTests;
describe("ensureGlobalUndiciStreamTimeouts", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({
DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
ensureGlobalUndiciEnvProxyDispatcher,
ensureGlobalUndiciStreamTimeouts,
resetGlobalUndiciStreamTimeoutsForTests,
} = await import("./undici-global-dispatcher.js"));
});
beforeEach(() => {
vi.clearAllMocks();
resetGlobalUndiciStreamTimeoutsForTests();
setCurrentDispatcher(new Agent());
@ -238,5 +240,4 @@ afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
resolveOutboundTarget: vi.fn(() => ({ ok: true as const, to: "+1999" })),
@ -73,9 +73,11 @@ import type { OpenClawConfig } from "../../config/config.js";
let resolveAgentDeliveryPlan: typeof import("./agent-delivery.js").resolveAgentDeliveryPlan;
let resolveAgentOutboundTarget: typeof import("./agent-delivery.js").resolveAgentOutboundTarget;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ resolveAgentDeliveryPlan, resolveAgentOutboundTarget } = await import("./agent-delivery.js"));
});
beforeEach(() => {
mocks.resolveOutboundTarget.mockClear();
mocks.resolveSessionDeliveryTarget.mockClear();
});

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
listChannelPlugins: vi.fn(),
@ -21,8 +21,7 @@ let listConfiguredMessageChannels: ChannelSelectionModule["listConfiguredMessage
let resolveMessageChannelSelection: ChannelSelectionModule["resolveMessageChannelSelection"];
let runtimeModule: RuntimeModule;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
runtimeModule = await import("../../runtime.js");
({ __testing, listConfiguredMessageChannels, resolveMessageChannelSelection } =
await import("./channel-selection.js"));

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const resolveAgentIdentityMock = vi.hoisted(() => vi.fn());
const resolveAgentAvatarMock = vi.hoisted(() => vi.fn());
@ -16,11 +16,15 @@ type IdentityModule = typeof import("./identity.js");
let normalizeOutboundIdentity: IdentityModule["normalizeOutboundIdentity"];
let resolveAgentOutboundIdentity: IdentityModule["resolveAgentOutboundIdentity"];
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ normalizeOutboundIdentity, resolveAgentOutboundIdentity } = await import("./identity.js"));
});
beforeEach(() => {
resolveAgentIdentityMock.mockReset();
resolveAgentAvatarMock.mockReset();
});
describe("normalizeOutboundIdentity", () => {
it.each([
{

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";
import type { ChannelOutboundAdapter, ChannelPlugin } from "../../channels/plugins/types.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import {
@ -22,9 +22,11 @@ vi.mock("../../gateway/call.js", () => ({
let sendMessage: typeof import("./message.js").sendMessage;
let sendPoll: typeof import("./message.js").sendPoll;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ sendMessage, sendPoll } = await import("./message.js"));
});
beforeEach(() => {
callGatewayMock.mockClear();
setRegistry(emptyRegistry);
});

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
getChannelPlugin: vi.fn(),
@ -7,46 +7,58 @@ const mocks = vi.hoisted(() => ({
resolveRuntimePluginRegistry: vi.fn(),
}));
vi.mock("../../channels/plugins/index.js", () => ({
normalizeChannelId: (channel?: string) => channel?.trim().toLowerCase() ?? undefined,
getChannelPlugin: mocks.getChannelPlugin,
listChannelPlugins: () => [],
}));
vi.mock("../../agents/agent-scope.js", () => ({
resolveDefaultAgentId: () => "main",
resolveSessionAgentId: ({
sessionKey,
}: {
sessionKey?: string;
config?: unknown;
agentId?: string;
}) => {
const match = sessionKey?.match(/^agent:([^:]+)/i);
return match?.[1] ?? "main";
},
resolveAgentWorkspaceDir: () => "/tmp/openclaw-test-workspace",
}));
vi.mock("../../config/plugin-auto-enable.js", () => ({
applyPluginAutoEnable: ({ config }: { config: unknown }) => ({ config, changes: [] }),
}));
vi.mock("../../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: mocks.resolveRuntimePluginRegistry,
}));
vi.mock("./targets.js", () => ({
resolveOutboundTarget: mocks.resolveOutboundTarget,
}));
vi.mock("./deliver.js", () => ({
deliverOutboundPayloads: mocks.deliverOutboundPayloads,
}));
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
let sendMessage: typeof import("./message.js").sendMessage;
let resetOutboundChannelResolutionStateForTest: typeof import("./channel-resolution.js").resetOutboundChannelResolutionStateForTest;
describe("sendMessage", () => {
beforeEach(async () => {
vi.resetModules();
vi.doMock("../../channels/plugins/index.js", () => ({
normalizeChannelId: (channel?: string) => channel?.trim().toLowerCase() ?? undefined,
getChannelPlugin: mocks.getChannelPlugin,
listChannelPlugins: () => [],
}));
vi.doMock("../../agents/agent-scope.js", () => ({
resolveDefaultAgentId: () => "main",
resolveSessionAgentId: ({
sessionKey,
}: {
sessionKey?: string;
config?: unknown;
agentId?: string;
}) => {
const match = sessionKey?.match(/^agent:([^:]+)/i);
return match?.[1] ?? "main";
},
resolveAgentWorkspaceDir: () => "/tmp/openclaw-test-workspace",
}));
vi.doMock("../../config/plugin-auto-enable.js", () => ({
applyPluginAutoEnable: ({ config }: { config: unknown }) => ({ config, changes: [] }),
}));
vi.doMock("../../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: mocks.resolveRuntimePluginRegistry,
}));
vi.doMock("./targets.js", () => ({
resolveOutboundTarget: mocks.resolveOutboundTarget,
}));
vi.doMock("./deliver.js", () => ({
deliverOutboundPayloads: mocks.deliverOutboundPayloads,
}));
beforeAll(async () => {
({ sendMessage } = await import("./message.js"));
({ resetOutboundChannelResolutionStateForTest } = await import("./channel-resolution.js"));
});
beforeEach(() => {
setActivePluginRegistry(createTestRegistry([]));
resetOutboundChannelResolutionStateForTest();
mocks.getChannelPlugin.mockClear();
mocks.resolveOutboundTarget.mockClear();
mocks.deliverOutboundPayloads.mockClear();
@ -57,8 +69,6 @@ describe("sendMessage", () => {
});
mocks.resolveOutboundTarget.mockImplementation(({ to }: { to: string }) => ({ ok: true, to }));
mocks.deliverOutboundPayloads.mockResolvedValue([{ channel: "mattermost", messageId: "m1" }]);
({ sendMessage } = await import("./message.js"));
});
it("passes explicit agentId to outbound delivery for scoped media roots", async () => {

View File

@ -1,5 +1,5 @@
import { Container, Separator, TextDisplay } from "@buape/carbon";
import { beforeEach, describe, expect, it } from "vitest";
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import { vi } from "vitest";
import type { ChannelMessageActionName } from "../../channels/plugins/types.js";
import type { OpenClawConfig } from "../../config/config.js";
@ -53,6 +53,19 @@ const mocks = vi.hoisted(() => ({
),
}));
vi.mock("./channel-adapters.js", () => ({
getChannelMessageAdapter: mocks.getChannelMessageAdapter,
}));
vi.mock("./target-normalization.js", () => ({
normalizeTargetForProvider: mocks.normalizeTargetForProvider,
}));
vi.mock("./target-resolver.js", () => ({
formatTargetDisplay: mocks.formatTargetDisplay,
lookupDirectoryDisplay: mocks.lookupDirectoryDisplay,
}));
const slackConfig = {
channels: {
slack: {
@ -96,19 +109,7 @@ function expectCrossContextPolicyResult(params: {
}
describe("outbound policy helpers", () => {
beforeEach(async () => {
vi.resetModules();
vi.clearAllMocks();
vi.doMock("./channel-adapters.js", () => ({
getChannelMessageAdapter: mocks.getChannelMessageAdapter,
}));
vi.doMock("./target-normalization.js", () => ({
normalizeTargetForProvider: mocks.normalizeTargetForProvider,
}));
vi.doMock("./target-resolver.js", () => ({
formatTargetDisplay: mocks.formatTargetDisplay,
lookupDirectoryDisplay: mocks.lookupDirectoryDisplay,
}));
beforeAll(async () => {
({
applyCrossContextDecoration,
buildCrossContextDecoration,
@ -117,6 +118,10 @@ describe("outbound policy helpers", () => {
} = await import("./outbound-policy.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
it.each([
{
cfg: {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const resolveSessionAgentIdMock = vi.hoisted(() => vi.fn());
@ -6,15 +6,18 @@ type SessionContextModule = typeof import("./session-context.js");
let buildOutboundSessionContext: SessionContextModule["buildOutboundSessionContext"];
beforeEach(async () => {
vi.resetModules();
resolveSessionAgentIdMock.mockReset();
vi.doMock("../../agents/agent-scope.js", () => ({
resolveSessionAgentId: (...args: unknown[]) => resolveSessionAgentIdMock(...args),
}));
vi.mock("../../agents/agent-scope.js", () => ({
resolveSessionAgentId: (...args: unknown[]) => resolveSessionAgentIdMock(...args),
}));
beforeAll(async () => {
({ buildOutboundSessionContext } = await import("./session-context.js"));
});
beforeEach(() => {
resolveSessionAgentIdMock.mockReset();
});
describe("buildOutboundSessionContext", () => {
it("returns undefined when both session key and agent id are blank", () => {
expect(

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const normalizeChannelIdMock = vi.hoisted(() => vi.fn());
const getChannelPluginMock = vi.hoisted(() => vi.fn());
@ -9,26 +9,31 @@ type TargetNormalizationModule = typeof import("./target-normalization.js");
let buildTargetResolverSignature: TargetNormalizationModule["buildTargetResolverSignature"];
let normalizeChannelTargetInput: TargetNormalizationModule["normalizeChannelTargetInput"];
let normalizeTargetForProvider: TargetNormalizationModule["normalizeTargetForProvider"];
let resetTargetNormalizerCacheForTests: TargetNormalizationModule["__testing"]["resetTargetNormalizerCacheForTests"];
async function loadTargetNormalizationModule() {
vi.doMock("../../channels/plugins/index.js", () => ({
normalizeChannelId: (...args: unknown[]) => normalizeChannelIdMock(...args),
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
}));
vi.doMock("../../plugins/runtime.js", () => ({
getActivePluginChannelRegistryVersion: (...args: unknown[]) =>
getActivePluginChannelRegistryVersionMock(...args),
}));
vi.mock("../../channels/plugins/index.js", () => ({
normalizeChannelId: (...args: unknown[]) => normalizeChannelIdMock(...args),
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
}));
vi.mock("../../plugins/runtime.js", () => ({
getActivePluginChannelRegistryVersion: (...args: unknown[]) =>
getActivePluginChannelRegistryVersionMock(...args),
}));
beforeAll(async () => {
({ buildTargetResolverSignature, normalizeChannelTargetInput, normalizeTargetForProvider } =
await import("./target-normalization.js"));
}
({
__testing: { resetTargetNormalizerCacheForTests },
} = await import("./target-normalization.js"));
});
beforeEach(async () => {
vi.resetModules();
beforeEach(() => {
normalizeChannelIdMock.mockReset();
getChannelPluginMock.mockReset();
getActivePluginChannelRegistryVersionMock.mockReset();
await loadTargetNormalizationModule();
resetTargetNormalizerCacheForTests();
});
describe("normalizeChannelTargetInput", () => {

View File

@ -14,6 +14,14 @@ type TargetNormalizerCacheEntry = {
const targetNormalizerCacheByChannelId = new Map<string, TargetNormalizerCacheEntry>();
function resetTargetNormalizerCacheForTests(): void {
targetNormalizerCacheByChannelId.clear();
}
export const __testing = {
resetTargetNormalizerCacheForTests,
} as const;
function resolveTargetNormalizer(channelId: ChannelId): TargetNormalizer {
const version = getActivePluginChannelRegistryVersion();
const cached = targetNormalizerCacheByChannelId.get(channelId);

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 { ChannelDirectoryEntry } from "../../channels/plugins/types.js";
import type { OpenClawConfig } from "../../config/config.js";
type TargetResolverModule = typeof import("./target-resolver.js");
@ -17,8 +17,21 @@ const mocks = vi.hoisted(() => ({
getActivePluginChannelRegistryVersion: vi.fn(() => 1),
}));
beforeEach(async () => {
vi.resetModules();
vi.mock("../../channels/plugins/index.js", () => ({
getChannelPlugin: (...args: unknown[]) => mocks.getChannelPlugin(...args),
normalizeChannelId: (value: string) => value,
}));
vi.mock("../../plugins/runtime.js", () => ({
getActivePluginChannelRegistryVersion: () => mocks.getActivePluginChannelRegistryVersion(),
}));
beforeAll(async () => {
({ resetDirectoryCache, resolveMessagingTarget, formatTargetDisplay } =
await import("./target-resolver.js"));
});
beforeEach(() => {
mocks.listPeers.mockReset();
mocks.listPeersLive.mockReset();
mocks.listGroups.mockReset();
@ -27,15 +40,7 @@ beforeEach(async () => {
mocks.getChannelPlugin.mockReset();
mocks.getActivePluginChannelRegistryVersion.mockReset();
mocks.getActivePluginChannelRegistryVersion.mockReturnValue(1);
vi.doMock("../../channels/plugins/index.js", () => ({
getChannelPlugin: (...args: unknown[]) => mocks.getChannelPlugin(...args),
normalizeChannelId: (value: string) => value,
}));
vi.doMock("../../plugins/runtime.js", () => ({
getActivePluginChannelRegistryVersion: () => mocks.getActivePluginChannelRegistryVersion(),
}));
({ resetDirectoryCache, resolveMessagingTarget, formatTargetDisplay } =
await import("./target-resolver.js"));
resetDirectoryCache();
});
async function expectOkResolution(

View File

@ -1,111 +0,0 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
getChannelPlugin: vi.fn(),
resolveRuntimePluginRegistry: vi.fn(),
}));
const TEST_WORKSPACE_ROOT = "/tmp/openclaw-test-workspace";
function normalizeChannel(value?: string) {
return value?.trim().toLowerCase() ?? undefined;
}
function applyPluginAutoEnableForTests(config: unknown) {
return { config, changes: [] as unknown[] };
}
function createTelegramPlugin() {
return {
id: "telegram",
meta: { label: "Telegram" },
config: {
listAccountIds: () => [],
resolveAccount: () => ({}),
},
};
}
vi.mock("../../channels/plugins/index.js", () => ({
getChannelPlugin: mocks.getChannelPlugin,
normalizeChannelId: normalizeChannel,
}));
vi.mock("../../agents/agent-scope.js", () => ({
resolveDefaultAgentId: () => "main",
resolveAgentWorkspaceDir: () => TEST_WORKSPACE_ROOT,
}));
vi.mock("../../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: mocks.resolveRuntimePluginRegistry,
}));
vi.mock("../../config/plugin-auto-enable.js", () => ({
applyPluginAutoEnable(args: { config: unknown }) {
return applyPluginAutoEnableForTests(args.config);
},
}));
let setActivePluginRegistry: typeof import("../../plugins/runtime.js").setActivePluginRegistry;
let createTestRegistry: typeof import("../../test-utils/channel-plugins.js").createTestRegistry;
let resetOutboundChannelResolutionStateForTest: typeof import("./channel-resolution.js").resetOutboundChannelResolutionStateForTest;
let resolveOutboundTarget: typeof import("./targets.js").resolveOutboundTarget;
describe("resolveOutboundTarget channel resolution", () => {
let registrySeq = 0;
const resolveTelegramTarget = () =>
resolveOutboundTarget({
channel: "telegram",
to: "123456",
cfg: { channels: { telegram: { botToken: "test-token" } } },
mode: "explicit",
});
beforeAll(async () => {
vi.resetModules();
({ setActivePluginRegistry } = await import("../../plugins/runtime.js"));
({ createTestRegistry } = await import("../../test-utils/channel-plugins.js"));
({ resetOutboundChannelResolutionStateForTest } = await import("./channel-resolution.js"));
({ resolveOutboundTarget } = await import("./targets.js"));
});
beforeEach(() => {
registrySeq += 1;
resetOutboundChannelResolutionStateForTest();
setActivePluginRegistry(createTestRegistry([]), `targets-test-${registrySeq}`);
mocks.getChannelPlugin.mockReset();
mocks.resolveRuntimePluginRegistry.mockReset();
});
it("recovers telegram plugin resolution so announce delivery does not fail with Unsupported channel: telegram", () => {
const telegramPlugin = createTelegramPlugin();
mocks.getChannelPlugin.mockReturnValueOnce(undefined).mockReturnValueOnce(telegramPlugin);
const result = resolveTelegramTarget();
expect(result).toEqual({ ok: true, to: "123456" });
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledTimes(1);
});
it("retries bootstrap on subsequent resolve when the first bootstrap attempt fails", () => {
const telegramPlugin = createTelegramPlugin();
mocks.getChannelPlugin
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(telegramPlugin)
.mockReturnValue(telegramPlugin);
mocks.resolveRuntimePluginRegistry
.mockImplementationOnce(() => {
throw new Error("bootstrap failed");
})
.mockImplementation(() => undefined);
const first = resolveTelegramTarget();
const second = resolveTelegramTarget();
expect(first.ok).toBe(false);
expect(second).toEqual({ ok: true, to: "123456" });
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledTimes(2);
});
});

View File

@ -1,5 +1,5 @@
import { Buffer } from "node:buffer";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const randomBytesMock = vi.hoisted(() => vi.fn());
@ -17,12 +17,15 @@ let generatePairingToken: PairingTokenModule["generatePairingToken"];
let PAIRING_TOKEN_BYTES: PairingTokenModule["PAIRING_TOKEN_BYTES"];
let verifyPairingToken: PairingTokenModule["verifyPairingToken"];
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ generatePairingToken, PAIRING_TOKEN_BYTES, verifyPairingToken } =
await import("./pairing-token.js"));
});
beforeEach(() => {
randomBytesMock.mockReset();
});
describe("generatePairingToken", () => {
it("uses the configured byte count and returns a base64url token", () => {
randomBytesMock.mockReturnValueOnce(Buffer.from([0xfb, 0xff, 0x00]));

View File

@ -61,6 +61,11 @@ function makePluginRequest(overrides?: Partial<PluginApprovalRequest>): PluginAp
};
}
async function flushPendingDelivery(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
}
describe("plugin approval forwarding", () => {
beforeEach(() => {
setActivePluginRegistry(emptyRegistry);
@ -78,10 +83,8 @@ describe("plugin approval forwarding", () => {
const { forwarder } = createForwarder({ cfg: PLUGIN_TARGETS_CFG, deliver });
const result = await forwarder.handlePluginApprovalRequested!(makePluginRequest());
expect(result).toBe(true);
// Allow delivery to be async
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
const deliveryArgs = deliver.mock.calls[0]?.[0] as
| { payloads?: Array<{ text?: string; interactive?: unknown }> }
| undefined;
@ -123,9 +126,8 @@ describe("plugin approval forwarding", () => {
const request = makePluginRequest();
request.request.severity = "critical";
await forwarder.handlePluginApprovalRequested!(request);
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
const text =
(deliver.mock.calls[0]?.[0] as { payloads?: Array<{ text?: string }> })?.payloads?.[0]
?.text ?? "";
@ -159,9 +161,8 @@ describe("plugin approval forwarding", () => {
const { forwarder } = createForwarder({ cfg, deliver });
const result = await forwarder.handlePluginApprovalRequested!(makePluginRequest());
expect(result).toBe(true);
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
});
it("returns false when no approvals config at all", async () => {
@ -196,9 +197,8 @@ describe("plugin approval forwarding", () => {
const deliver = vi.fn().mockResolvedValue([]);
const { forwarder } = createForwarder({ cfg: PLUGIN_TARGETS_CFG, deliver });
await forwarder.handlePluginApprovalRequested!(makePluginRequest());
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
const deliveryArgs = deliver.mock.calls[0]?.[0] as
| { payloads?: Array<{ text?: string }> }
| undefined;
@ -225,9 +225,8 @@ describe("plugin approval forwarding", () => {
const deliver = vi.fn().mockResolvedValue([]);
const { forwarder } = createForwarder({ cfg: PLUGIN_TARGETS_CFG, deliver });
await forwarder.handlePluginApprovalRequested!(makePluginRequest());
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
expect(beforeDeliverPayload).toHaveBeenCalled();
});
@ -256,9 +255,8 @@ describe("plugin approval forwarding", () => {
// First register request so targets are tracked
await forwarder.handlePluginApprovalRequested!(makePluginRequest());
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
deliver.mockClear();
const resolved: PluginApprovalResolved = {
@ -268,6 +266,7 @@ describe("plugin approval forwarding", () => {
ts: 2000,
};
await forwarder.handlePluginApprovalResolved!(resolved);
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
const deliveryArgs = deliver.mock.calls[0]?.[0] as
| { payloads?: Array<{ text?: string }> }
@ -283,9 +282,8 @@ describe("plugin approval forwarding", () => {
// First register request so targets are tracked
await forwarder.handlePluginApprovalRequested!(makePluginRequest());
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
deliver.mockClear();
const resolved: PluginApprovalResolved = {
@ -337,10 +335,8 @@ describe("plugin approval forwarding", () => {
const deliver = vi.fn().mockResolvedValue([]);
const { forwarder } = createForwarder({ cfg: PLUGIN_TARGETS_CFG, deliver });
await forwarder.handlePluginApprovalRequested!(makePluginRequest());
// Wait for the async delivery to flush before stopping
await vi.waitFor(() => {
expect(deliver).toHaveBeenCalled();
});
await flushPendingDelivery();
expect(deliver).toHaveBeenCalled();
forwarder.stop();
deliver.mockClear();
// After stop, resolved should not deliver

View File

@ -1,5 +1,5 @@
import net from "node:net";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { stripAnsi } from "../terminal/ansi.js";
const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn());
@ -15,12 +15,15 @@ let PortInUseError: typeof import("./ports.js").PortInUseError;
const describeUnix = process.platform === "win32" ? describe.skip : describe;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ inspectPortUsage } = await import("./ports-inspect.js"));
({ ensurePortAvailable, handlePortError, PortInUseError } = await import("./ports.js"));
});
beforeEach(() => {
runCommandWithTimeoutMock.mockReset();
});
describe("ports helpers", () => {
it("ensurePortAvailable rejects when port busy", async () => {
const server = net.createServer();
@ -66,10 +69,6 @@ describe("ports helpers", () => {
});
describeUnix("inspectPortUsage", () => {
beforeEach(() => {
runCommandWithTimeoutMock.mockClear();
});
it("reports busy when lsof is missing but loopback listener exists", async () => {
const server = net.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const resolveProviderUsageAuthWithPluginMock = vi.fn(
async (..._args: unknown[]): Promise<unknown> => null,
@ -15,11 +15,13 @@ vi.mock("../plugins/provider-runtime.js", async (importOriginal) => {
let resolveProviderAuths: typeof import("./provider-usage.auth.js").resolveProviderAuths;
describe("resolveProviderAuths plugin boundary", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ resolveProviderAuths } = await import("./provider-usage.auth.js"));
});
beforeEach(() => {
resolveProviderUsageAuthWithPluginMock.mockReset();
resolveProviderUsageAuthWithPluginMock.mockResolvedValue(null);
({ resolveProviderAuths } = await import("./provider-usage.auth.js"));
});
it("prefers plugin-owned usage auth when available", async () => {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createProviderUsageFetch } from "../test-utils/provider-usage-fetch.js";
const resolveProviderUsageSnapshotWithPluginMock = vi.fn();
@ -17,11 +17,13 @@ let loadProviderUsageSummary: typeof import("./provider-usage.load.js").loadProv
const usageNow = Date.UTC(2026, 0, 7, 0, 0, 0);
describe("provider-usage.load plugin boundary", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ loadProviderUsageSummary } = await import("./provider-usage.load.js"));
});
beforeEach(() => {
resolveProviderUsageSnapshotWithPluginMock.mockReset();
resolveProviderUsageSnapshotWithPluginMock.mockResolvedValue(null);
({ loadProviderUsageSummary } = await import("./provider-usage.load.js"));
});
it("prefers plugin-owned usage snapshots", async () => {

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";
// This entire file tests lsof-based Unix port polling. The feature is a deliberate
// no-op on Windows (findGatewayPidsOnPortSync returns [] immediately). Running these
@ -86,13 +86,12 @@ function installInitialBusyPoll(
}
describe.skipIf(isWindows)("restart-stale-pids", () => {
beforeEach(() => {
vi.resetModules();
});
beforeEach(async () => {
beforeAll(async () => {
({ __testing, cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } =
await import("./restart-stale-pids.js"));
});
beforeEach(() => {
mockSpawnSync.mockReset();
mockResolveGatewayPort.mockReset();
mockRestartWarn.mockReset();

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";
const spawnSyncMock = vi.hoisted(() => vi.fn());
const resolveLsofCommandSyncMock = vi.hoisted(() => vi.fn());
@ -30,27 +30,12 @@ let findGatewayPidsOnPortSync: typeof import("./restart-stale-pids.js").findGate
let currentTimeMs = 0;
beforeEach(async () => {
vi.resetModules();
vi.doMock("node:child_process", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:child_process")>();
return {
...actual,
spawnSync: (...args: Parameters<typeof actual.spawnSync>) => spawnSyncMock(...args),
};
});
vi.doMock("./ports-lsof.js", () => ({
resolveLsofCommandSync: (...args: unknown[]) => resolveLsofCommandSyncMock(...args),
}));
vi.doMock("../config/paths.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/paths.js")>();
return {
...actual,
resolveGatewayPort: (...args: unknown[]) => resolveGatewayPortMock(...args),
};
});
beforeAll(async () => {
({ __testing, cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } =
await import("./restart-stale-pids.js"));
});
beforeEach(() => {
spawnSyncMock.mockReset();
resolveLsofCommandSyncMock.mockReset();
resolveGatewayPortMock.mockReset();

View File

@ -1,5 +1,5 @@
import { Buffer } from "node:buffer";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const cryptoMocks = vi.hoisted(() => ({
randomBytes: vi.fn((bytes: number) => Buffer.alloc(bytes, 0xab)),
@ -19,8 +19,7 @@ let generateSecureInt: typeof import("./secure-random.js").generateSecureInt;
let generateSecureToken: typeof import("./secure-random.js").generateSecureToken;
let generateSecureUuid: typeof import("./secure-random.js").generateSecureUuid;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({
generateSecureFraction,
generateSecureHex,
@ -30,6 +29,11 @@ beforeEach(async () => {
} = await import("./secure-random.js"));
});
beforeEach(() => {
cryptoMocks.randomBytes.mockClear();
cryptoMocks.randomUUID.mockReset();
});
describe("secure-random", () => {
it("delegates UUID generation to crypto.randomUUID", () => {
cryptoMocks.randomUUID.mockReturnValueOnce("uuid-1").mockReturnValueOnce("uuid-2");

View File

@ -1,50 +1,54 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const transportReadyMocks = vi.hoisted(() => ({
injectedSleepError: null as Error | null,
}));
let injectedSleepError: Error | null = null;
type TransportReadyModule = typeof import("./transport-ready.js");
let waitForTransportReady: TransportReadyModule["waitForTransportReady"];
vi.mock("./backoff.js", () => ({
sleepWithAbort: async (ms: number, signal?: AbortSignal) => {
if (transportReadyMocks.injectedSleepError) {
throw transportReadyMocks.injectedSleepError;
}
if (signal?.aborted) {
throw new Error("aborted");
}
if (ms <= 0) {
return;
}
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, ms);
const onAbort = () => {
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
reject(new Error("aborted"));
};
signal?.addEventListener("abort", onAbort, { once: true });
});
},
}));
function createRuntime() {
return { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
}
describe("waitForTransportReady", () => {
beforeEach(async () => {
vi.useFakeTimers();
vi.resetModules();
// Perf: `sleepWithAbort` uses `node:timers/promises` which isn't controlled by fake timers.
// Route sleeps through global `setTimeout` so tests can advance time deterministically.
vi.doMock("./backoff.js", () => ({
sleepWithAbort: async (ms: number, signal?: AbortSignal) => {
if (injectedSleepError) {
throw injectedSleepError;
}
if (signal?.aborted) {
throw new Error("aborted");
}
if (ms <= 0) {
return;
}
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, ms);
const onAbort = () => {
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
reject(new Error("aborted"));
};
signal?.addEventListener("abort", onAbort, { once: true });
});
},
}));
beforeAll(async () => {
({ waitForTransportReady } = await import("./transport-ready.js"));
});
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
injectedSleepError = null;
transportReadyMocks.injectedSleepError = null;
});
it("returns when the check succeeds and logs after the delay", async () => {
@ -154,7 +158,7 @@ describe("waitForTransportReady", () => {
it("rethrows non-abort sleep failures", async () => {
const runtime = createRuntime();
injectedSleepError = new Error("sleep exploded");
transportReadyMocks.injectedSleepError = new Error("sleep exploded");
await expect(
waitForTransportReady({

View File

@ -1,7 +1,7 @@
import fs from "node:fs";
import os from "node:os";
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 { captureFullEnv } from "../test-utils/env.js";
const spawnMock = vi.hoisted(() => vi.fn());
@ -53,11 +53,16 @@ afterEach(() => {
});
describe("relaunchGatewayScheduledTask", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ relaunchGatewayScheduledTask } = await import("./windows-task-restart.js"));
});
beforeEach(() => {
spawnMock.mockReset();
resolvePreferredOpenClawTmpDirMock.mockReset();
resolvePreferredOpenClawTmpDirMock.mockReturnValue(os.tmpdir());
});
it("writes a detached schtasks relaunch helper", () => {
const unref = vi.fn();
let seenCommandArg = "";

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";
import { captureEnv } from "../test-utils/env.js";
const readFileSyncMock = vi.hoisted(() => vi.fn());
@ -32,16 +32,15 @@ function setPlatform(platform: NodeJS.Platform): void {
describe("wsl detection", () => {
let envSnapshot: ReturnType<typeof captureEnv>;
beforeAll(async () => {
({ isWSLEnv, isWSLSync, isWSL2Sync, isWSL, resetWSLStateForTests } = await import("./wsl.js"));
});
beforeEach(() => {
vi.resetModules();
envSnapshot = captureEnv(["WSL_INTEROP", "WSL_DISTRO_NAME", "WSLENV"]);
readFileSyncMock.mockReset();
readFileMock.mockReset();
setPlatform("linux");
});
beforeEach(async () => {
({ isWSLEnv, isWSLSync, isWSL2Sync, isWSL, resetWSLStateForTests } = await import("./wsl.js"));
resetWSLStateForTests();
});

View File

@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { readLoggingConfig } from "./config.js";
const loadConfigMock = vi.hoisted(() => vi.fn());
@ -24,8 +25,6 @@ describe("readLoggingConfig", () => {
throw new Error("loadConfig should not be called");
});
const { readLoggingConfig } = await import("./config.js");
expect(readLoggingConfig()).toBeUndefined();
expect(loadConfigMock).not.toHaveBeenCalled();
});

View File

@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
withBundledPluginAllowlistCompat,
@ -13,11 +13,22 @@ import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
const { resolveRuntimePluginRegistryMock } = vi.hoisted(() => ({
resolveRuntimePluginRegistryMock: vi.fn<
(params?: unknown) => ReturnType<typeof createEmptyPluginRegistry> | undefined
>(() => undefined),
}));
vi.mock("../plugins/loader.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../plugins/loader.js")>();
return {
...actual,
resolveRuntimePluginRegistry: resolveRuntimePluginRegistryMock,
};
});
let describeImageFile: typeof import("./runtime.js").describeImageFile;
let runMediaUnderstandingFile: typeof import("./runtime.js").runMediaUnderstandingFile;
let resolveRuntimePluginRegistryMock: ReturnType<
typeof vi.fn<(params?: unknown) => ReturnType<typeof createEmptyPluginRegistry> | undefined>
>;
function setCompatibleActiveMediaUnderstandingRegistry(
pluginRegistry: ReturnType<typeof createEmptyPluginRegistry>,
@ -53,21 +64,13 @@ function setCompatibleActiveMediaUnderstandingRegistry(
}
describe("media-understanding runtime helpers", () => {
beforeAll(async () => {
({ describeImageFile, runMediaUnderstandingFile } = await import("./runtime.js"));
});
afterEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
vi.doUnmock("../plugins/loader.js");
});
beforeEach(async () => {
vi.resetModules();
resolveRuntimePluginRegistryMock = vi.fn<
(params?: unknown) => ReturnType<typeof createEmptyPluginRegistry> | undefined
>(() => undefined);
vi.doMock("../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: resolveRuntimePluginRegistryMock,
}));
({ describeImageFile, runMediaUnderstandingFile } = await import("./runtime.js"));
});
it("describes images through the active media-understanding registry", async () => {

View File

@ -46,7 +46,6 @@ describe("media server outside-workspace mapping", () => {
beforeAll(async () => {
vi.useRealTimers();
vi.doUnmock("undici");
vi.resetModules();
const require = createRequire(import.meta.url);
({ SafeOpenError } = await import("../infra/fs-safe.js"));
({ startMediaServer } = await import("./server.js"));

View File

@ -105,7 +105,6 @@ describe("media server", () => {
beforeAll(async () => {
vi.useRealTimers();
vi.doUnmock("undici");
vi.resetModules();
const require = createRequire(import.meta.url);
({ startMediaServer } = await import("./server.js"));
({ MEDIA_MAX_BYTES } = await import("./store.js"));

View File

@ -1,6 +1,6 @@
import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
const mocks = vi.hoisted(() => ({
@ -32,13 +32,9 @@ describe("media store outside-workspace mapping", () => {
let tempHome: TempHomeEnv;
let home = "";
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ saveMediaSource } = await import("./store.js"));
({ SafeOpenError } = await import("../infra/fs-safe.js"));
});
beforeAll(async () => {
tempHome = await createTempHomeEnv("openclaw-media-store-test-home-");
home = tempHome.home;
});

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";
import type { SecretInput } from "../config/types.secrets.js";
vi.mock("../infra/device-bootstrap.js", () => ({
@ -158,16 +158,18 @@ describe("pairing setup code", () => {
}
beforeEach(() => {
vi.resetModules();
vi.stubEnv("OPENCLAW_GATEWAY_TOKEN", "");
vi.stubEnv("OPENCLAW_GATEWAY_PASSWORD", "");
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "");
});
beforeEach(async () => {
beforeAll(async () => {
({ encodePairingSetupCode, resolvePairingSetupFromConfig } = await import("./setup-code.js"));
({ issueDeviceBootstrapToken: issueDeviceBootstrapTokenMock } =
await import("../infra/device-bootstrap.js"));
});
beforeEach(() => {
vi.mocked(issueDeviceBootstrapTokenMock).mockClear();
});

View File

@ -43,10 +43,10 @@ describe("enqueueKeyedTask", () => {
},
});
await vi.waitFor(() => {
expect(order).toContain("a1:start");
expect(order).toContain("b1:start");
});
await Promise.resolve();
await Promise.resolve();
expect(order).toContain("a1:start");
expect(order).toContain("b1:start");
expect(order).not.toContain("a2:start");
gate.resolve();

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const loadWebMediaMock = vi.hoisted(() => vi.fn());
@ -11,9 +11,11 @@ type OutboundMediaModule = typeof import("./outbound-media.js");
let loadOutboundMediaFromUrl: OutboundMediaModule["loadOutboundMediaFromUrl"];
describe("loadOutboundMediaFromUrl", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ loadOutboundMediaFromUrl } = await import("./outbound-media.js"));
});
beforeEach(() => {
loadWebMediaMock.mockReset();
});

View File

@ -1,8 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { BUNDLED_WEB_SEARCH_PLUGIN_IDS } from "./bundled-web-search-ids.js";
import { loadPluginManifestRegistry } from "./manifest-registry.js";
let hasBundledWebSearchCredential: typeof import("./bundled-web-search-registry.js").hasBundledWebSearchCredential;
let listBundledWebSearchProviders: typeof import("./bundled-web-search.js").listBundledWebSearchProviders;
let resolveBundledWebSearchPluginIds: typeof import("./bundled-web-search.js").resolveBundledWebSearchPluginIds;
function resolveManifestBundledWebSearchPluginIds() {
return loadPluginManifestRegistry({})
.plugins.filter(
@ -14,13 +18,18 @@ function resolveManifestBundledWebSearchPluginIds() {
}
async function resolveRegistryBundledWebSearchPluginIds() {
const { listBundledWebSearchProviders } = await import("./bundled-web-search.js");
return listBundledWebSearchProviders()
.map(({ pluginId }) => pluginId)
.filter((value, index, values) => values.indexOf(value) === index)
.toSorted((left, right) => left.localeCompare(right));
}
beforeAll(async () => {
({ listBundledWebSearchProviders, resolveBundledWebSearchPluginIds } =
await import("./bundled-web-search.js"));
({ hasBundledWebSearchCredential } = await import("./bundled-web-search-registry.js"));
});
function expectBundledWebSearchIds(actual: readonly string[], expected: readonly string[]) {
expect(actual).toEqual(expected);
}
@ -33,12 +42,7 @@ function expectBundledWebSearchAlignment(params: {
}
describe("bundled web search metadata", () => {
beforeEach(() => {
vi.resetModules();
});
it("keeps bundled web search compat ids aligned with bundled manifests", async () => {
const { resolveBundledWebSearchPluginIds } = await import("./bundled-web-search.js");
expectBundledWebSearchAlignment({
actual: resolveBundledWebSearchPluginIds({}),
expected: resolveManifestBundledWebSearchPluginIds(),
@ -54,10 +58,6 @@ describe("bundled web search metadata", () => {
});
describe("hasBundledWebSearchCredential", () => {
beforeEach(() => {
vi.resetModules();
});
const baseCfg = {
agents: { defaults: { model: { primary: "ollama/mistral-8b" } } },
browser: { enabled: false },
@ -103,7 +103,6 @@ describe("hasBundledWebSearchCredential", () => {
env: { OPENROUTER_API_KEY: "sk-or-v1-test" },
},
] as const)("$name", async ({ config, env }) => {
const { hasBundledWebSearchCredential } = await import("./bundled-web-search-registry.js");
expect(hasBundledWebSearchCredential({ config, env })).toBe(true);
});
});

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 { OpenClawConfig } from "../config/config.js";
import { createEmptyPluginRegistry } from "./registry.js";
@ -132,8 +132,11 @@ function expectCompatChainApplied(params: {
}
describe("resolvePluginCapabilityProviders", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ resolvePluginCapabilityProviders } = await import("./capability-provider-runtime.js"));
});
beforeEach(() => {
mocks.resolveRuntimePluginRegistry.mockReset();
mocks.resolveRuntimePluginRegistry.mockReturnValue(undefined);
mocks.loadPluginManifestRegistry.mockReset();
@ -144,7 +147,6 @@ describe("resolvePluginCapabilityProviders", () => {
mocks.withBundledPluginEnablementCompat.mockImplementation(({ config }) => config);
mocks.withBundledPluginVitestCompat.mockReset();
mocks.withBundledPluginVitestCompat.mockImplementation(({ config }) => config);
({ resolvePluginCapabilityProviders } = await import("./capability-provider-runtime.js"));
});
it("uses the active registry when capability providers are already loaded", () => {

View File

@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { withEnvAsync } from "../test-utils/env.js";
const installPluginFromPathMock = vi.fn();
@ -20,6 +20,9 @@ const fetchWithSsrFGuardMock = vi.hoisted(() =>
}),
);
const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn());
let installPluginFromMarketplace: typeof import("./marketplace.js").installPluginFromMarketplace;
let listMarketplacePlugins: typeof import("./marketplace.js").listMarketplacePlugins;
let resolveMarketplaceInstallShortcut: typeof import("./marketplace.js").resolveMarketplaceInstallShortcut;
vi.mock("./install.js", () => ({
installPluginFromPath: (...args: unknown[]) => installPluginFromPathMock(...args),
@ -38,6 +41,11 @@ vi.mock("../process/exec.js", () => ({
runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args),
}));
beforeAll(async () => {
({ installPluginFromMarketplace, listMarketplacePlugins, resolveMarketplaceInstallShortcut } =
await import("./marketplace.js"));
});
async function withTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T> {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-marketplace-test-"));
try {
@ -96,7 +104,6 @@ function mockRemoteMarketplaceClone(params: { manifest: unknown; pluginDir?: str
async function expectRemoteMarketplaceError(params: { manifest: unknown; expectedError: string }) {
mockRemoteMarketplaceClone({ manifest: params.manifest });
const { listMarketplacePlugins } = await import("./marketplace.js");
const result = await listMarketplacePlugins({ marketplace: "owner/repo" });
expect(result).toEqual({
@ -188,7 +195,6 @@ describe("marketplace plugins", () => {
],
});
const { listMarketplacePlugins } = await import("./marketplace.js");
expectMarketplaceManifestListing(await listMarketplacePlugins({ marketplace: rootDir }));
});
});
@ -216,7 +222,6 @@ describe("marketplace plugins", () => {
extensions: ["index.ts"],
});
const { installPluginFromMarketplace } = await import("./marketplace.js");
const result = await installPluginFromMarketplace({
marketplace: manifestPath,
plugin: "frontend-design",
@ -248,7 +253,6 @@ describe("marketplace plugins", () => {
}),
);
const { resolveMarketplaceInstallShortcut } = await import("./marketplace.js");
const shortcut = await withEnvAsync(
{ HOME: homeDir, OPENCLAW_HOME: openClawHome },
async () => await resolveMarketplaceInstallShortcut("superpowers@claude-plugins-official"),
@ -283,7 +287,6 @@ describe("marketplace plugins", () => {
extensions: ["index.ts"],
});
const { installPluginFromMarketplace } = await import("./marketplace.js");
const result = await installPluginFromMarketplace({
marketplace: "owner/repo",
plugin: "frontend-design",
@ -307,7 +310,6 @@ describe("marketplace plugins", () => {
],
});
const { installPluginFromMarketplace } = await import("./marketplace.js");
const result = await installPluginFromMarketplace({
marketplace: manifestPath,
plugin: "frontend-design",

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const resolveRuntimePluginRegistryMock = vi.fn();
const applyPluginAutoEnableMock = vi.fn();
@ -97,8 +97,15 @@ async function expectCloseMemoryRuntimeCase(params: {
}
describe("memory runtime auto-enable loading", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({
getActiveMemorySearchManager,
resolveActiveMemoryBackendConfig,
closeActiveMemorySearchManagers,
} = await import("./memory-runtime.js"));
});
beforeEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
applyPluginAutoEnableMock.mockReset();
getMemoryRuntimeMock.mockReset();
@ -106,11 +113,6 @@ describe("memory runtime auto-enable loading", () => {
config: params.config,
changes: [],
}));
({
getActiveMemorySearchManager,
resolveActiveMemoryBackendConfig,
closeActiveMemorySearchManagers,
} = await import("./memory-runtime.js"));
});
it.each([

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const loadOpenClawPluginsMock = vi.fn();
const loadPluginManifestRegistryMock = vi.fn();
@ -163,8 +163,12 @@ function expectBundledProviderLoad(params?: { config?: unknown; env?: NodeJS.Pro
}
describe("resolvePluginProviders", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ resolveOwningPluginIdsForProvider } = await import("./providers.js"));
({ resolvePluginProviders } = await import("./providers.runtime.js"));
});
beforeEach(() => {
loadOpenClawPluginsMock.mockReset();
loadOpenClawPluginsMock.mockReturnValue({
providers: [{ pluginId: "google", provider: { id: "demo-provider" } }],
@ -187,8 +191,6 @@ describe("resolvePluginProviders", () => {
origin: "workspace",
}),
]);
({ resolveOwningPluginIdsForProvider } = await import("./providers.js"));
({ resolvePluginProviders } = await import("./providers.runtime.js"));
});
it("forwards an explicit env to plugin loading", () => {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
createCompatibilityNotice,
createCustomHook,
@ -214,8 +214,19 @@ function expectBundleInspectState(
}
describe("buildPluginStatusReport", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({
buildAllPluginInspectReports,
buildPluginCompatibilityNotices,
buildPluginCompatibilityWarnings,
buildPluginInspectReport,
buildPluginStatusReport,
formatPluginCompatibilityNotice,
summarizePluginCompatibility,
} = await import("./status.js"));
});
beforeEach(() => {
loadConfigMock.mockReset();
loadOpenClawPluginsMock.mockReset();
applyPluginAutoEnableMock.mockReset();
@ -235,15 +246,6 @@ describe("buildPluginStatusReport", () => {
(params: { config: unknown }) => params.config,
);
setPluginLoadResult({ plugins: [] });
({
buildAllPluginInspectReports,
buildPluginCompatibilityNotices,
buildPluginCompatibilityWarnings,
buildPluginInspectReport,
buildPluginStatusReport,
formatPluginCompatibilityNotice,
summarizePluginCompatibility,
} = await import("./status.js"));
});
it("forwards an explicit env to plugin loading", () => {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../test/helpers/import-fresh.js";
import { CommandLane } from "./lanes.js";
@ -56,8 +56,7 @@ function enqueueBlockedMainTask<T = void>(
}
describe("command queue", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({
clearCommandLane,
CommandLaneClearedError,
@ -72,6 +71,9 @@ describe("command queue", () => {
setCommandLaneConcurrency,
waitForActiveTasks,
} = await import("./command-queue.js"));
});
beforeEach(() => {
resetCommandQueueStateForTest();
// Queue state is global across module instances, so reset main lane
// concurrency explicitly to avoid cross-file leakage.
@ -250,9 +252,7 @@ describe("command queue", () => {
await blocker;
});
await vi.waitFor(() => {
expect(getActiveTaskCount()).toBeGreaterThanOrEqual(1);
});
expect(getActiveTaskCount()).toBeGreaterThanOrEqual(1);
// Enqueue another task — it should be stuck behind the blocker
let task2Ran = false;
@ -260,9 +260,7 @@ describe("command queue", () => {
task2Ran = true;
});
await vi.waitFor(() => {
expect(getQueueSize(lane)).toBeGreaterThanOrEqual(2);
});
expect(getQueueSize(lane)).toBeGreaterThanOrEqual(2);
expect(task2Ran).toBe(false);
// Simulate SIGUSR1: reset all lanes. Queued work (task2) should be
@ -396,10 +394,8 @@ describe("command queue", () => {
return "done";
});
await vi.waitFor(() => {
expect(commandQueueB.getQueueSize(lane)).toBe(1);
expect(commandQueueB.getActiveTaskCount()).toBe(1);
});
expect(commandQueueB.getQueueSize(lane)).toBe(1);
expect(commandQueueB.getActiveTaskCount()).toBe(1);
release();
await expect(task).resolves.toBe("done");

View File

@ -57,6 +57,7 @@ describe("runCommandWithTimeout no-output timer", () => {
beforeEach(async () => {
vi.resetModules();
({ runCommandWithTimeout } = await import("./exec.js"));
spawnMock.mockClear();
});
afterEach(() => {

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";
const { spawnMock } = vi.hoisted(() => ({
spawnMock: vi.fn(),
@ -25,9 +25,11 @@ async function withPlatform<T>(platform: NodeJS.Platform, run: () => Promise<T>
describe("killProcessTree", () => {
let killSpy: ReturnType<typeof vi.spyOn>;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ killProcessTree } = await import("./kill-tree.js"));
});
beforeEach(() => {
spawnMock.mockClear();
killSpy = vi.spyOn(process, "kill");
vi.useFakeTimers();

View File

@ -1,7 +1,7 @@
import type { ChildProcess } from "node:child_process";
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const { spawnWithFallbackMock, killProcessTreeMock } = vi.hoisted(() => ({
spawnWithFallbackMock: vi.fn(),
@ -54,9 +54,11 @@ async function createAdapterHarness(params?: {
describe("createChildAdapter", () => {
const originalServiceMarker = process.env.OPENCLAW_SERVICE_MARKER;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ createChildAdapter } = await import("./child.js"));
});
beforeEach(() => {
spawnWithFallbackMock.mockClear();
killProcessTreeMock.mockClear();
delete process.env.OPENCLAW_SERVICE_MARKER;

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";
const { spawnMock, ptyKillMock, killProcessTreeMock } = vi.hoisted(() => ({
spawnMock: vi.fn(),
@ -39,9 +39,11 @@ function expectSpawnEnv() {
describe("createPtyAdapter", () => {
let createPtyAdapter: typeof import("./pty.js").createPtyAdapter;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ createPtyAdapter } = await import("./pty.js"));
});
beforeEach(() => {
spawnMock.mockClear();
ptyKillMock.mockClear();
killProcessTreeMock.mockClear();

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const { createPtyAdapterMock } = vi.hoisted(() => ({
createPtyAdapterMock: vi.fn(),
@ -35,9 +35,11 @@ function createStubPtyAdapter() {
describe("process supervisor PTY command contract", () => {
let createProcessSupervisor: typeof import("./supervisor.js").createProcessSupervisor;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ createProcessSupervisor } = await import("./supervisor.js"));
});
beforeEach(() => {
createPtyAdapterMock.mockClear();
});

View File

@ -1,11 +1,9 @@
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
const selectMock = vi.hoisted(() => vi.fn());
const createSecretsConfigIOMock = vi.hoisted(() => vi.fn());
const readJsonObjectIfExistsMock = vi.hoisted(() => vi.fn());
const mockedModuleIds = ["@clack/prompts", "./config-io.js", "./storage-scan.js"] as const;
vi.mock("@clack/prompts", () => ({
confirm: vi.fn(),
select: (...args: unknown[]) => selectMock(...args),
@ -29,13 +27,6 @@ describe("runSecretsConfigureInteractive", () => {
readJsonObjectIfExistsMock.mockReset();
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
it("does not load auth-profiles when running providers-only", async () => {
Object.defineProperty(process.stdin, "isTTY", {
value: true,

View File

@ -1,4 +1,4 @@
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
@ -12,11 +12,6 @@ const { resolveBundledPluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolveBundledPluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const mockedModuleIds = [
"../plugins/web-search-providers.js",
"../plugins/web-search-providers.runtime.js",
] as const;
let bundledWebSearchProviders: typeof import("../plugins/web-search-providers.js");
let runtimeWebSearchProviders: typeof import("../plugins/web-search-providers.runtime.js");
let secretResolve: typeof import("./resolve.js");
@ -219,12 +214,6 @@ describe("runtime web tools resolution", () => {
vi.restoreAllMocks();
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
});
it("keeps web search disabled when search config is absent", async () => {
const bundledProviderSpy = vi.mocked(
bundledWebSearchProviders.resolveBundledPluginWebSearchProviders,

View File

@ -1,4 +1,4 @@
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, 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";
@ -13,11 +13,6 @@ const { resolveBundledPluginWebSearchProvidersMock, resolvePluginWebSearchProvid
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const mockedModuleIds = [
"../plugins/web-search-providers.js",
"../plugins/web-search-providers.runtime.js",
] as const;
let clearSecretsRuntimeSnapshot: typeof import("./runtime.js").clearSecretsRuntimeSnapshot;
let prepareSecretsRuntimeSnapshot: typeof import("./runtime.js").prepareSecretsRuntimeSnapshot;
@ -261,13 +256,6 @@ describe("secrets runtime target coverage", () => {
({ clearSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot } = await import("./runtime.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
it("handles every openclaw.json registry target when configured as active", async () => {
const entries = listSecretTargetRegistryEntries().filter(
(entry) => entry.configFile === "openclaw.json",

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, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { 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";
@ -14,11 +14,6 @@ const { resolveBundledPluginWebSearchProvidersMock, resolvePluginWebSearchProvid
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const mockedModuleIds = [
"../plugins/web-search-providers.js",
"../plugins/web-search-providers.runtime.js",
] as const;
vi.mock("../plugins/web-search-providers.js", () => ({
resolveBundledPluginWebSearchProviders: resolveBundledPluginWebSearchProvidersMock,
}));
@ -125,7 +120,6 @@ function loadAuthStoreWithProfiles(profiles: AuthProfileStore["profiles"]): Auth
describe("secrets runtime snapshot", () => {
beforeAll(async () => {
vi.resetModules();
({ clearConfigCache, clearRuntimeConfigSnapshot } = await import("../config/config.js"));
({
activateSecretsRuntimeSnapshot,
@ -150,13 +144,6 @@ describe("secrets runtime snapshot", () => {
resolvePluginWebSearchProvidersMock.mockReset();
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
it("resolves env refs for config and auth profiles", async () => {
const config = asConfig({
agents: {

View File

@ -25,7 +25,6 @@ let resolveWindowsUserPrincipal: typeof import("./windows-acl.js").resolveWindow
let summarizeWindowsAcl: typeof import("./windows-acl.js").summarizeWindowsAcl;
beforeAll(async () => {
vi.resetModules();
({
createIcaclsResetCommand,
formatIcaclsResetCommand,

View File

@ -10,6 +10,7 @@ import { withTempDir } from "../test-helpers/temp-dir.js";
import { installInMemoryTaskAndFlowRegistryRuntime } from "../test-utils/task-flow-registry-runtime.js";
import { createFlowRecord, getFlowById, resetFlowRegistryForTests } from "./flow-registry.js";
import {
cancelTaskById,
createTaskRecord,
findLatestTaskForSessionKey,
findTaskByRunId,
@ -62,22 +63,6 @@ vi.mock("../agents/subagent-control.js", () => ({
killSubagentRunAdmin: (params: unknown) => hoisted.killSubagentRunAdminMock(params),
}));
async function loadFreshTaskRegistryModulesForControlTest() {
vi.resetModules();
vi.doMock("./task-registry-delivery-runtime.js", () => ({
sendMessage: hoisted.sendMessageMock,
}));
vi.doMock("../acp/control-plane/manager.js", () => ({
getAcpSessionManager: () => ({
cancelSession: hoisted.cancelSessionMock,
}),
}));
vi.doMock("../agents/subagent-control.js", () => ({
killSubagentRunAdmin: (params: unknown) => hoisted.killSubagentRunAdminMock(params),
}));
return await import("./task-registry.js");
}
async function waitForAssertion(assertion: () => void, timeoutMs = 2_000, stepMs = 5) {
const startedAt = Date.now();
for (;;) {
@ -1498,120 +1483,110 @@ describe("task-registry", () => {
});
it("cancels ACP-backed tasks through the ACP session manager", async () => {
await withTaskRegistryTempDir(async (root) => {
const registry = await loadFreshTaskRegistryModulesForControlTest();
await withTempDir({ prefix: "openclaw-task-registry-" }, async (root) => {
process.env.OPENCLAW_STATE_DIR = root;
registry.resetTaskRegistryForTests();
try {
hoisted.cancelSessionMock.mockResolvedValue(undefined);
resetTaskRegistryForTests();
hoisted.cancelSessionMock.mockResolvedValue(undefined);
const task = registry.createTaskRecord({
runtime: "acp",
requesterSessionKey: "agent:main:main",
requesterOrigin: {
const task = createTaskRecord({
runtime: "acp",
requesterSessionKey: "agent:main:main",
requesterOrigin: {
channel: "telegram",
to: "telegram:123",
},
childSessionKey: "agent:codex:acp:child",
runId: "run-cancel-acp",
task: "Investigate issue",
status: "running",
deliveryStatus: "pending",
});
const result = await cancelTaskById({
cfg: {} as never,
taskId: task.taskId,
});
expect(hoisted.cancelSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
cfg: {},
sessionKey: "agent:codex:acp:child",
reason: "task-cancel",
}),
);
expect(result).toMatchObject({
found: true,
cancelled: true,
task: expect.objectContaining({
taskId: task.taskId,
status: "cancelled",
error: "Cancelled by operator.",
}),
});
await waitForAssertion(() =>
expect(hoisted.sendMessageMock).toHaveBeenCalledWith(
expect.objectContaining({
channel: "telegram",
to: "telegram:123",
},
childSessionKey: "agent:codex:acp:child",
runId: "run-cancel-acp",
task: "Investigate issue",
status: "running",
deliveryStatus: "pending",
});
const result = await registry.cancelTaskById({
cfg: {} as never,
taskId: task.taskId,
});
expect(hoisted.cancelSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
cfg: {},
sessionKey: "agent:codex:acp:child",
reason: "task-cancel",
content: "Background task cancelled: ACP background task (run run-canc).",
}),
);
expect(result).toMatchObject({
found: true,
cancelled: true,
task: expect.objectContaining({
taskId: task.taskId,
status: "cancelled",
error: "Cancelled by operator.",
}),
});
await waitForAssertion(() =>
expect(hoisted.sendMessageMock).toHaveBeenCalledWith(
expect.objectContaining({
channel: "telegram",
to: "telegram:123",
content: "Background task cancelled: ACP background task (run run-canc).",
}),
),
);
} finally {
registry.resetTaskRegistryForTests();
}
),
);
});
});
it("cancels subagent-backed tasks through subagent control", async () => {
await withTaskRegistryTempDir(async (root) => {
const registry = await loadFreshTaskRegistryModulesForControlTest();
await withTempDir({ prefix: "openclaw-task-registry-" }, async (root) => {
process.env.OPENCLAW_STATE_DIR = root;
registry.resetTaskRegistryForTests();
try {
hoisted.killSubagentRunAdminMock.mockResolvedValue({
found: true,
killed: true,
});
resetTaskRegistryForTests();
hoisted.killSubagentRunAdminMock.mockResolvedValue({
found: true,
killed: true,
});
const task = registry.createTaskRecord({
runtime: "subagent",
requesterSessionKey: "agent:main:main",
requesterOrigin: {
const task = createTaskRecord({
runtime: "subagent",
requesterSessionKey: "agent:main:main",
requesterOrigin: {
channel: "telegram",
to: "telegram:123",
},
childSessionKey: "agent:worker:subagent:child",
runId: "run-cancel-subagent",
task: "Investigate issue",
status: "running",
deliveryStatus: "pending",
});
const result = await cancelTaskById({
cfg: {} as never,
taskId: task.taskId,
});
expect(hoisted.killSubagentRunAdminMock).toHaveBeenCalledWith(
expect.objectContaining({
cfg: {},
sessionKey: "agent:worker:subagent:child",
}),
);
expect(result).toMatchObject({
found: true,
cancelled: true,
task: expect.objectContaining({
taskId: task.taskId,
status: "cancelled",
error: "Cancelled by operator.",
}),
});
await waitForAssertion(() =>
expect(hoisted.sendMessageMock).toHaveBeenCalledWith(
expect.objectContaining({
channel: "telegram",
to: "telegram:123",
},
childSessionKey: "agent:worker:subagent:child",
runId: "run-cancel-subagent",
task: "Investigate issue",
status: "running",
deliveryStatus: "pending",
});
const result = await registry.cancelTaskById({
cfg: {} as never,
taskId: task.taskId,
});
expect(hoisted.killSubagentRunAdminMock).toHaveBeenCalledWith(
expect.objectContaining({
cfg: {},
sessionKey: "agent:worker:subagent:child",
content: "Background task cancelled: Subagent task (run run-canc).",
}),
);
expect(result).toMatchObject({
found: true,
cancelled: true,
task: expect.objectContaining({
taskId: task.taskId,
status: "cancelled",
error: "Cancelled by operator.",
}),
});
await waitForAssertion(() =>
expect(hoisted.sendMessageMock).toHaveBeenCalledWith(
expect.objectContaining({
channel: "telegram",
to: "telegram:123",
content: "Background task cancelled: Subagent task (run run-canc).",
}),
),
);
} finally {
registry.resetTaskRegistryForTests();
}
),
);
});
});
});

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";
import type { OpenClawConfig } from "../config/config.js";
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
import type { SpeechProviderPlugin } from "../plugins/types.js";
@ -31,10 +31,7 @@ function createSpeechProvider(id: string, aliases?: string[]): SpeechProviderPlu
}
describe("speech provider registry", () => {
beforeEach(async () => {
vi.resetModules();
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
beforeAll(async () => {
({
getSpeechProvider,
listSpeechProviders,
@ -43,6 +40,11 @@ describe("speech provider registry", () => {
} = await import("./provider-registry.js"));
});
beforeEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
});
afterEach(() => {});
it("uses active plugin speech providers without reloading plugins", () => {

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";
import type { OpenClawConfig } from "../config/config.js";
import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
@ -64,15 +64,17 @@ describe("web search runtime", () => {
let activateSecretsRuntimeSnapshot: typeof import("../secrets/runtime.js").activateSecretsRuntimeSnapshot;
let clearSecretsRuntimeSnapshot: typeof import("../secrets/runtime.js").clearSecretsRuntimeSnapshot;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ runWebSearch } = await import("./runtime.js"));
({ activateSecretsRuntimeSnapshot, clearSecretsRuntimeSnapshot } =
await import("../secrets/runtime.js"));
});
beforeEach(() => {
resolveBundledPluginWebSearchProvidersMock.mockReset();
resolveRuntimeWebSearchProvidersMock.mockReset();
resolveBundledPluginWebSearchProvidersMock.mockReturnValue([]);
resolveRuntimeWebSearchProvidersMock.mockReturnValue([]);
({ runWebSearch } = await import("./runtime.js"));
({ activateSecretsRuntimeSnapshot, clearSecretsRuntimeSnapshot } =
await import("../secrets/runtime.js"));
});
afterEach(() => {

View File

@ -7,10 +7,6 @@
"durationMs": 6483.10009765625,
"testCount": 5
},
"src/infra/outbound/targets.channel-resolution.test.ts": {
"durationMs": 5879.394287109375,
"testCount": 2
},
"src/cron/service.issue-regressions.test.ts": {
"durationMs": 4524.16552734375,
"testCount": 39