mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-21 05:32:53 +00:00
perf: optimize test import surfaces
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user