perf: optimize test import surfaces

This commit is contained in:
Peter Steinberger
2026-04-11 03:07:44 +01:00
parent 84fb20aa52
commit 1c7444dab6
7 changed files with 101 additions and 91 deletions

View File

@@ -3,32 +3,21 @@ import * as commandRegistryModule from "openclaw/plugin-sdk/command-auth";
import type { ChatCommandDefinition, CommandArgsParsing } from "openclaw/plugin-sdk/command-auth";
import type { ModelsProviderData } from "openclaw/plugin-sdk/command-auth";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import * as pluginRuntimeModule from "openclaw/plugin-sdk/plugin-runtime";
import * as dispatcherModule from "openclaw/plugin-sdk/reply-dispatch-runtime";
import * as globalsModule from "openclaw/plugin-sdk/runtime-env";
import * as commandTextModule from "openclaw/plugin-sdk/text-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as modelPickerPreferencesModule from "./model-picker-preferences.js";
import * as modelPickerModule from "./model-picker.js";
import { createModelsProviderData as createBaseModelsProviderData } from "./model-picker.test-utils.js";
import * as nativeCommandRouteModule from "./native-command-route.js";
import { replyWithDiscordModelPickerProviders } from "./native-command-ui.js";
import {
__testing as nativeCommandTesting,
createDiscordModelPickerFallbackButton,
createDiscordModelPickerFallbackSelect,
} from "./native-command.js";
replyWithDiscordModelPickerProviders,
type DispatchDiscordCommandInteraction,
} from "./native-command-ui.js";
import { createNoopThreadBindingManager, type ThreadBindingManager } from "./thread-bindings.js";
vi.mock("openclaw/plugin-sdk/agent-runtime", () => ({
resolveDefaultModelForAgent: () => ({
provider: "anthropic",
model: "claude-sonnet-4.5",
}),
resolveHumanDelayConfig: () => undefined,
}));
type ModelPickerContext = Parameters<typeof createDiscordModelPickerFallbackButton>[0];
type ModelPickerContext = Parameters<typeof createDiscordModelPickerFallbackButton>[0]["ctx"];
type PickerButton = ReturnType<typeof createDiscordModelPickerFallbackButton>;
type PickerSelect = ReturnType<typeof createDiscordModelPickerFallbackSelect>;
type PickerButtonInteraction = Parameters<PickerButton["run"]>[0];
@@ -165,12 +154,43 @@ function createModelsViewSubmitData(): PickerButtonData {
};
}
async function safeInteractionCall<T>(_label: string, fn: () => Promise<T>): Promise<T | null> {
return await fn();
}
function createDispatchSpy() {
return vi.fn<DispatchDiscordCommandInteraction>().mockResolvedValue();
}
function createModelPickerFallbackButton(
context: ModelPickerContext,
dispatchCommandInteraction: DispatchDiscordCommandInteraction = createDispatchSpy(),
) {
return createDiscordModelPickerFallbackButton({
ctx: context,
safeInteractionCall,
dispatchCommandInteraction,
});
}
function createModelPickerFallbackSelect(
context: ModelPickerContext,
dispatchCommandInteraction: DispatchDiscordCommandInteraction = createDispatchSpy(),
) {
return createDiscordModelPickerFallbackSelect({
ctx: context,
safeInteractionCall,
dispatchCommandInteraction,
});
}
async function runSubmitButton(params: {
context: ModelPickerContext;
data: PickerButtonData;
dispatchCommandInteraction?: DispatchDiscordCommandInteraction;
userId?: string;
}) {
const button = createDiscordModelPickerFallbackButton(params.context);
const button = createModelPickerFallbackButton(params.context, params.dispatchCommandInteraction);
const submitInteraction = createInteraction({ userId: params.userId ?? "owner" });
await button.run(submitInteraction as unknown as PickerButtonInteraction, params.data);
return submitInteraction;
@@ -179,10 +199,11 @@ async function runSubmitButton(params: {
async function runModelSelect(params: {
context: ModelPickerContext;
data?: PickerSelectData;
dispatchCommandInteraction?: DispatchDiscordCommandInteraction;
userId?: string;
values?: string[];
}) {
const select = createDiscordModelPickerFallbackSelect(params.context);
const select = createModelPickerFallbackSelect(params.context, params.dispatchCommandInteraction);
const selectInteraction = createInteraction({
userId: params.userId ?? "owner",
values: params.values ?? ["gpt-4o"],
@@ -195,24 +216,12 @@ async function runModelSelect(params: {
}
function expectDispatchedModelSelection(params: {
dispatchSpy: { mock: { calls: Array<[unknown]> } };
dispatchSpy: ReturnType<typeof createDispatchSpy>;
model: string;
requireTargetSessionKey?: boolean;
}) {
const dispatchCall = params.dispatchSpy.mock.calls[0]?.[0] as {
ctx?: {
CommandBody?: string;
CommandArgs?: { values?: { model?: string } };
CommandTargetSessionKey?: string;
};
};
expect(dispatchCall.ctx?.CommandBody).toBe(`/model ${params.model}`);
expect(dispatchCall.ctx?.CommandArgs?.values?.model).toBe(params.model);
if (params.requireTargetSessionKey) {
if (!dispatchCall.ctx?.CommandTargetSessionKey) {
throw new Error("model selection dispatch did not include a target session key");
}
}
const dispatchCall = params.dispatchSpy.mock.calls[0]?.[0];
expect(dispatchCall?.prompt).toBe(`/model ${params.model}`);
expect(dispatchCall?.commandArgs?.values?.model).toBe(params.model);
}
function createBoundThreadBindingManager(params: {
@@ -246,26 +255,10 @@ function createBoundThreadBindingManager(params: {
};
}
function createDispatchSpy() {
const dispatchSpy = vi
.fn<typeof dispatcherModule.dispatchReplyWithDispatcher>()
.mockResolvedValue({} as never);
nativeCommandTesting.setDispatchReplyWithDispatcher(dispatchSpy);
return dispatchSpy;
}
describe("Discord model picker interactions", () => {
beforeEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
nativeCommandTesting.setMatchPluginCommand(pluginRuntimeModule.matchPluginCommand);
nativeCommandTesting.setExecutePluginCommand(pluginRuntimeModule.executePluginCommand);
nativeCommandTesting.setDispatchReplyWithDispatcher(
dispatcherModule.dispatchReplyWithDispatcher,
);
nativeCommandTesting.setResolveDiscordNativeInteractionRouteState(
nativeCommandRouteModule.resolveDiscordNativeInteractionRouteState,
);
});
afterEach(() => {
@@ -274,8 +267,8 @@ describe("Discord model picker interactions", () => {
it("registers distinct fallback ids for button and select handlers", () => {
const context = createModelPickerContext();
const button = createDiscordModelPickerFallbackButton(context);
const select = createDiscordModelPickerFallbackSelect(context);
const button = createModelPickerFallbackButton(context);
const select = createModelPickerFallbackSelect(context);
expect(button.customId).not.toBe(select.customId);
expect(button.customId.split(":")[0]).toBe(
@@ -289,7 +282,7 @@ describe("Discord model picker interactions", () => {
it("ignores interactions from users other than the picker owner", async () => {
const context = createModelPickerContext();
const loadSpy = vi.spyOn(modelPickerModule, "loadDiscordModelPickerData");
const button = createDiscordModelPickerFallbackButton(context);
const button = createModelPickerFallbackButton(context);
const interaction = createInteraction({ userId: "intruder" });
const data: PickerButtonData = {
@@ -317,7 +310,10 @@ describe("Discord model picker interactions", () => {
const dispatchSpy = createDispatchSpy();
const selectInteraction = await runModelSelect({ context });
const selectInteraction = await runModelSelect({
context,
dispatchCommandInteraction: dispatchSpy,
});
expect(selectInteraction.update).toHaveBeenCalledTimes(1);
expect(dispatchSpy).not.toHaveBeenCalled();
@@ -325,6 +321,7 @@ describe("Discord model picker interactions", () => {
const submitInteraction = await runSubmitButton({
context,
data: createModelsViewSubmitData(),
dispatchCommandInteraction: dispatchSpy,
});
expect(submitInteraction.update).toHaveBeenCalledTimes(1);
@@ -332,7 +329,6 @@ describe("Discord model picker interactions", () => {
expectDispatchedModelSelection({
dispatchSpy,
model: "openai/gpt-4o",
requireTargetSessionKey: true,
});
});
@@ -352,9 +348,9 @@ describe("Discord model picker interactions", () => {
.spyOn(commandTextModule, "withTimeout")
.mockRejectedValue(new Error("timeout"));
await runModelSelect({ context });
await runModelSelect({ context, dispatchCommandInteraction: dispatchSpy });
const button = createDiscordModelPickerFallbackButton(context);
const button = createModelPickerFallbackButton(context, dispatchSpy);
const submitInteraction = createInteraction({ userId: "owner" });
const submitData = createModelsViewSubmitData();
@@ -384,7 +380,7 @@ describe("Discord model picker interactions", () => {
"anthropic/claude-sonnet-4-5",
]);
const button = createDiscordModelPickerFallbackButton(context);
const button = createModelPickerFallbackButton(context);
const interaction = createInteraction({ userId: "owner" });
const data: PickerButtonData = {
@@ -433,6 +429,7 @@ describe("Discord model picker interactions", () => {
pg: "1",
rs: "2",
},
dispatchCommandInteraction: dispatchSpy,
});
expect(submitInteraction.update).toHaveBeenCalledTimes(1);
@@ -453,10 +450,10 @@ describe("Discord model picker interactions", () => {
vi.spyOn(modelPickerModule, "loadDiscordModelPickerData").mockResolvedValue(pickerData);
mockModelCommandPipeline(modelCommand);
createDispatchSpy();
const dispatchSpy = createDispatchSpy();
const verboseSpy = vi.spyOn(globalsModule, "logVerbose").mockImplementation(() => {});
const select = createDiscordModelPickerFallbackSelect(context);
const select = createModelPickerFallbackSelect(context, dispatchSpy);
const selectInteraction = createInteraction({
userId: "owner",
values: ["gpt-4o"],
@@ -468,7 +465,7 @@ describe("Discord model picker interactions", () => {
const selectData = createModelsViewSelectData();
await select.run(selectInteraction as unknown as PickerSelectInteraction, selectData);
const button = createDiscordModelPickerFallbackButton(context);
const button = createModelPickerFallbackButton(context, dispatchSpy);
const submitInteraction = createInteraction({ userId: "owner" });
submitInteraction.channel = {
type: ChannelType.PublicThread,

View File

@@ -1,7 +1,7 @@
import { RateLimitError } from "@buape/carbon";
import { ChannelType, Routes } from "discord-api-types/v10";
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { makeDiscordRest } from "./send.test-harness.js";
vi.mock("openclaw/plugin-sdk/web-media", async () => {
@@ -63,6 +63,14 @@ beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.useRealTimers();
});
afterAll(() => {
vi.doUnmock("openclaw/plugin-sdk/web-media");
});
describe("sendMessageDiscord", () => {
it("creates a thread", async () => {
const { rest, getMock, postMock } = makeDiscordRest();
@@ -475,29 +483,29 @@ describe("retry rate limits", () => {
});
it("uses retry_after delays when rate limited", async () => {
vi.useFakeTimers();
const setTimeoutSpy = vi.spyOn(global, "setTimeout");
const { rest, postMock } = makeDiscordRest();
const rateLimitError = createMockRateLimitError(0.5);
try {
const { rest, postMock } = makeDiscordRest();
const rateLimitError = createMockRateLimitError(0.001);
postMock
.mockRejectedValueOnce(rateLimitError)
.mockResolvedValueOnce({ id: "msg1", channel_id: "789" });
postMock
.mockRejectedValueOnce(rateLimitError)
.mockResolvedValueOnce({ id: "msg1", channel_id: "789" });
const promise = sendMessageDiscord("channel:789", "hello", {
rest,
token: "t",
retry: { attempts: 2, minDelayMs: 0, maxDelayMs: 1000, jitter: 0 },
});
const promise = sendMessageDiscord("channel:789", "hello", {
rest,
token: "t",
retry: { attempts: 2, minDelayMs: 0, maxDelayMs: 1000, jitter: 0 },
});
await vi.runAllTimersAsync();
await expect(promise).resolves.toEqual({
messageId: "msg1",
channelId: "789",
});
expect(setTimeoutSpy.mock.calls[0]?.[1]).toBe(500);
setTimeoutSpy.mockRestore();
vi.useRealTimers();
await expect(promise).resolves.toEqual({
messageId: "msg1",
channelId: "789",
});
expect(setTimeoutSpy.mock.calls[0]?.[1]).toBe(1);
} finally {
setTimeoutSpy.mockRestore();
}
});
it("stops after max retry attempts", async () => {

View File

@@ -438,6 +438,17 @@ describe("normalizeCronJobCreate", () => {
expect(payload.timeoutSeconds).toBe(0);
});
it("preserves fractional timeoutSeconds for short agentTurn deadlines", () => {
const normalized = normalizeCronJobCreate({
name: "fractional timeout",
schedule: { kind: "every", everyMs: 60_000 },
payload: { kind: "agentTurn", message: "hello", timeoutSeconds: 0.03 },
}) as unknown as Record<string, unknown>;
const payload = normalized.payload as Record<string, unknown>;
expect(payload.timeoutSeconds).toBe(0.03);
});
it("preserves empty toolsAllow lists for create jobs", () => {
const normalized = normalizeCronJobCreate({
name: "empty-tools",

View File

@@ -252,11 +252,7 @@ export const cronHandlers: GatewayRequestHandlers = {
result = await context.cron.enqueueRun(jobId, p.mode ?? "force");
} catch (error) {
if (isInvalidCronSessionTargetIdError(error)) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, formatErrorMessage(error)),
);
respond(true, { ok: true, ran: false, reason: "invalid-spec" }, undefined);
return;
}
throw error;

View File

@@ -12,13 +12,10 @@ const runtimeTaskMocks = vi.hoisted(() => ({
killSubagentRunAdminMock: vi.fn(),
}));
vi.mock("../../acp/control-plane/manager.js", () => ({
vi.mock("../../tasks/task-registry-control.runtime.js", () => ({
getAcpSessionManager: () => ({
cancelSession: runtimeTaskMocks.cancelSessionMock,
}),
}));
vi.mock("../../agents/subagent-control.js", () => ({
killSubagentRunAdmin: (params: unknown) => runtimeTaskMocks.killSubagentRunAdminMock(params),
}));

View File

@@ -34,7 +34,7 @@ process.env.VITEST = "true";
process.env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS ??= "60000";
// Vitest fork workers can load transitive lockfile helpers many times per worker.
// Raise listener budget to avoid noisy MaxListeners warnings and warning-stack overhead.
const TEST_PROCESS_MAX_LISTENERS = 128;
const TEST_PROCESS_MAX_LISTENERS = 256;
if (process.getMaxListeners() > 0 && process.getMaxListeners() < TEST_PROCESS_MAX_LISTENERS) {
process.setMaxListeners(TEST_PROCESS_MAX_LISTENERS);
}

View File

@@ -17,13 +17,14 @@ describe("projects vitest config", () => {
expect(baseConfig.test?.projects).toEqual([...rootVitestProjects]);
});
it("keeps root projects on the shared thread-first pool by default", () => {
it("keeps root projects on their expected pool defaults", () => {
expect(createGatewayVitestConfig().test.pool).toBe("threads");
expect(createAgentsVitestConfig().test.pool).toBe("threads");
expect(createCommandsLightVitestConfig().test.pool).toBe("threads");
expect(createCommandsVitestConfig().test.pool).toBe("threads");
expect(createPluginSdkLightVitestConfig().test.pool).toBe("threads");
expect(createUnitFastVitestConfig().test.pool).toBe("threads");
expect(createContractsVitestConfig().test.pool).toBe("forks");
});
it("keeps the contracts lane on the non-isolated fork runner by default", () => {