fix(test): reduce channel suite startup hotspots

This commit is contained in:
Vincent Koc
2026-03-22 17:36:32 -07:00
parent 976eefe373
commit b21bcf6eb6
4 changed files with 255 additions and 187 deletions

View File

@@ -12,7 +12,7 @@ import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import { buildPluginBindingApprovalCustomId } from "openclaw/plugin-sdk/conversation-runtime";
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.js";
import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.ts";
import {
clearDiscordComponentEntries,
registerDiscordComponentEntries,
@@ -51,6 +51,7 @@ import {
const readAllowFromStoreMock = vi.hoisted(() => vi.fn());
const upsertPairingRequestMock = vi.hoisted(() => vi.fn());
const enqueueSystemEventMock = vi.hoisted(() => vi.fn());
const dispatchReplyMock = vi.hoisted(() => vi.fn());
const recordInboundSessionMock = vi.hoisted(() => vi.fn());
const readSessionUpdatedAtMock = vi.hoisted(() => vi.fn());
@@ -87,6 +88,34 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
resolvePluginConversationBindingApprovalMock(...args),
buildPluginBindingResolvedText: (...args: unknown[]) =>
buildPluginBindingResolvedTextMock(...args),
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
};
});
vi.mock("openclaw/plugin-sdk/conversation-runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
resolvePluginConversationBindingApproval: (...args: unknown[]) =>
resolvePluginConversationBindingApprovalMock(...args),
buildPluginBindingResolvedText: (...args: unknown[]) =>
buildPluginBindingResolvedTextMock(...args),
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
};
});
vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
return {
...actual,
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
};
});
vi.mock("openclaw/plugin-sdk/infra-runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
return {
...actual,
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
};
});
@@ -111,14 +140,6 @@ vi.mock("../../../../src/auto-reply/reply/provider-dispatcher.js", async (import
};
});
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args),
};
});
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
return {
@@ -138,14 +159,15 @@ vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => {
});
describe("agent components", () => {
const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig;
const dmSessionKey = buildAgentSessionKey({
const defaultDmSessionKey = buildAgentSessionKey({
agentId: "main",
channel: "discord",
accountId: "default",
peer: { kind: "direct", id: "123456789" },
});
const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig;
const createBaseDmInteraction = (overrides: Record<string, unknown> = {}) => {
const reply = vi.fn().mockResolvedValue(undefined);
const defer = vi.fn().mockResolvedValue(undefined);
@@ -203,11 +225,9 @@ describe("agent components", () => {
const pairingText = String(reply.mock.calls[0]?.[0]?.content ?? "");
expect(pairingText).toContain("Pairing code:");
const code = pairingText.match(/Pairing code:\s*([A-Z2-9]{8})/)?.[1];
if (!code) {
throw new Error(`pairing reply did not include an 8-character pairing code: ${pairingText}`);
}
expect(code).toBeDefined();
expect(pairingText).toContain(`openclaw pairing approve discord ${code}`);
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]);
expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default");
});
@@ -226,7 +246,7 @@ describe("agent components", () => {
content: "You are not authorized to use this button.",
ephemeral: true,
});
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]);
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
});
@@ -243,7 +263,7 @@ describe("agent components", () => {
expect(defer).not.toHaveBeenCalled();
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
expect(peekSystemEvents(dmSessionKey)).toEqual([
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
"[Discord component: hello clicked by Alice#1234 (123456789)]",
]);
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
@@ -263,7 +283,7 @@ describe("agent components", () => {
expect(defer).not.toHaveBeenCalled();
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
expect(peekSystemEvents(dmSessionKey)).toEqual([
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
"[Discord component: hello clicked by Alice#1234 (123456789)]",
]);
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
@@ -285,7 +305,7 @@ describe("agent components", () => {
content: "DM interactions are disabled.",
ephemeral: true,
});
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]);
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
});
@@ -303,7 +323,7 @@ describe("agent components", () => {
expect(defer).not.toHaveBeenCalled();
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
expect(peekSystemEvents(dmSessionKey)).toEqual([
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
"[Discord select menu: hello interacted by Alice#1234 (123456789) (selected: alpha)]",
]);
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
@@ -322,7 +342,7 @@ describe("agent components", () => {
expect(defer).not.toHaveBeenCalled();
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
expect(peekSystemEvents(dmSessionKey)).toEqual([
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
"[Discord component: hello_cid clicked by Alice#1234 (123456789)]",
]);
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
@@ -341,7 +361,7 @@ describe("agent components", () => {
expect(defer).not.toHaveBeenCalled();
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
expect(peekSystemEvents(dmSessionKey)).toEqual([
expect(peekSystemEvents(defaultDmSessionKey)).toEqual([
"[Discord component: hello%2G clicked by Alice#1234 (123456789)]",
]);
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
@@ -502,7 +522,7 @@ describe("discord component interactions", () => {
lastDispatchCtx = undefined;
readAllowFromStoreMock.mockClear().mockResolvedValue([]);
upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true });
resetSystemEventsForTest();
enqueueSystemEventMock.mockClear();
dispatchReplyMock.mockClear().mockImplementation(async (params: DispatchParams) => {
lastDispatchCtx = params.ctx;
await params.dispatcherOptions.deliver({ text: "ok" });

View File

@@ -0,0 +1,209 @@
import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
import type { SignalDaemonExitEvent } from "./daemon.js";
import {
createMockSignalDaemonHandle,
config,
getSignalToolResultTestMocks,
installSignalToolResultTestHooks,
setSignalToolResultTestConfig,
} from "./monitor.tool-result.test-harness.js";
installSignalToolResultTestHooks();
vi.resetModules();
const { monitorSignalProvider } = await import("./monitor.js");
const { waitForTransportReadyMock, spawnSignalDaemonMock, streamMock } =
getSignalToolResultTestMocks();
const SIGNAL_BASE_URL = "http://127.0.0.1:8080";
type MonitorSignalProviderOptions = Parameters<typeof monitorSignalProvider>[0];
function createMonitorRuntime() {
return {
log: vi.fn(),
error: vi.fn(),
exit: ((code: number): never => {
throw new Error(`exit ${code}`);
}) as (code: number) => never,
};
}
function createSignalConfig(overrides: Record<string, unknown> = {}): Record<string, unknown> {
const base = config as OpenClawConfig;
const channels = (base.channels ?? {}) as Record<string, unknown>;
const signal = (channels.signal ?? {}) as Record<string, unknown>;
return {
...base,
channels: {
...channels,
signal: {
...signal,
autoStart: true,
dmPolicy: "open",
allowFrom: ["*"],
...overrides,
},
},
};
}
function setSignalAutoStartConfig(overrides: Record<string, unknown> = {}) {
setSignalToolResultTestConfig(createSignalConfig(overrides));
}
function createAutoAbortController() {
const abortController = new AbortController();
streamMock.mockImplementation(async () => {
abortController.abort();
return;
});
return abortController;
}
async function runMonitorWithMocks(opts: MonitorSignalProviderOptions) {
return monitorSignalProvider({
config: config as OpenClawConfig,
waitForTransportReady: waitForTransportReadyMock as any,
...opts,
});
}
function expectWaitForTransportReadyTimeout(timeoutMs: number) {
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
expect.objectContaining({
timeoutMs,
}),
);
}
describe("monitorSignalProvider autostart", () => {
it("uses bounded readiness checks when auto-starting the daemon", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
const abortController = createAutoAbortController();
await runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
abortSignal: abortController.signal,
runtime,
});
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
expect.objectContaining({
label: "signal daemon",
timeoutMs: 30_000,
logAfterMs: 10_000,
logIntervalMs: 10_000,
pollIntervalMs: 150,
runtime,
abortSignal: expect.any(AbortSignal),
}),
);
});
it("uses startupTimeoutMs override when provided", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig({ startupTimeoutMs: 60_000 });
const abortController = createAutoAbortController();
await runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
abortSignal: abortController.signal,
runtime,
startupTimeoutMs: 90_000,
});
expectWaitForTransportReadyTimeout(90_000);
});
it("caps startupTimeoutMs at 2 minutes", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig({ startupTimeoutMs: 180_000 });
const abortController = createAutoAbortController();
await runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
abortSignal: abortController.signal,
runtime,
});
expectWaitForTransportReadyTimeout(120_000);
});
it("fails fast when auto-started signal daemon exits during startup", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
spawnSignalDaemonMock.mockReturnValueOnce(
createMockSignalDaemonHandle({
exited: Promise.resolve({ source: "process", code: 1, signal: null }),
isExited: () => true,
}),
);
waitForTransportReadyMock.mockImplementationOnce(
async (params: { abortSignal?: AbortSignal | null }) => {
await new Promise<void>((_resolve, reject) => {
if (params.abortSignal?.aborted) {
reject(params.abortSignal.reason);
return;
}
params.abortSignal?.addEventListener(
"abort",
() => reject(params.abortSignal?.reason ?? new Error("aborted")),
{ once: true },
);
});
},
);
await expect(
runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
runtime,
}),
).rejects.toThrow(/signal daemon exited/i);
});
it("treats daemon exit after user abort as clean shutdown", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
const abortController = new AbortController();
let exited = false;
let resolveExit!: (value: SignalDaemonExitEvent) => void;
const exitedPromise = new Promise<SignalDaemonExitEvent>((resolve) => {
resolveExit = resolve;
});
const stop = vi.fn(() => {
if (exited) {
return;
}
exited = true;
resolveExit({ source: "process", code: null, signal: "SIGTERM" });
});
spawnSignalDaemonMock.mockReturnValueOnce(
createMockSignalDaemonHandle({
stop,
exited: exitedPromise,
isExited: () => exited,
}),
);
streamMock.mockImplementationOnce(async () => {
abortController.abort(new Error("stop"));
});
await expect(
runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
runtime,
abortSignal: abortController.signal,
}),
).resolves.toBeUndefined();
});
});

View File

@@ -2,9 +2,7 @@ import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
import type { SignalDaemonExitEvent } from "./daemon.js";
import {
createMockSignalDaemonHandle,
config,
flush,
getSignalToolResultTestMocks,
@@ -26,26 +24,11 @@ const {
enqueueSystemEventMock,
upsertPairingRequestMock,
waitForTransportReadyMock,
spawnSignalDaemonMock,
} = getSignalToolResultTestMocks();
const SIGNAL_BASE_URL = "http://127.0.0.1:8080";
type MonitorSignalProviderOptions = Parameters<typeof monitorSignalProvider>[0];
function createMonitorRuntime() {
return {
log: vi.fn(),
error: vi.fn(),
exit: ((code: number): never => {
throw new Error(`exit ${code}`);
}) as (code: number) => never,
};
}
function setSignalAutoStartConfig(overrides: Record<string, unknown> = {}) {
setSignalToolResultTestConfig(createSignalConfig(overrides));
}
function createSignalConfig(overrides: Record<string, unknown> = {}): Record<string, unknown> {
const base = config as OpenClawConfig;
const channels = (base.channels ?? {}) as Record<string, unknown>;
@@ -65,15 +48,6 @@ function createSignalConfig(overrides: Record<string, unknown> = {}): Record<str
};
}
function createAutoAbortController() {
const abortController = new AbortController();
streamMock.mockImplementation(async () => {
abortController.abort();
return;
});
return abortController;
}
async function runMonitorWithMocks(opts: MonitorSignalProviderOptions) {
return monitorSignalProvider({
config: config as OpenClawConfig,
@@ -163,143 +137,7 @@ function setReactionNotificationConfig(mode: "all" | "own", extra: Record<string
);
}
function expectWaitForTransportReadyTimeout(timeoutMs: number) {
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
expect.objectContaining({
timeoutMs,
}),
);
}
describe("monitorSignalProvider tool results", () => {
it("uses bounded readiness checks when auto-starting the daemon", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
const abortController = createAutoAbortController();
await runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
abortSignal: abortController.signal,
runtime,
});
expect(waitForTransportReadyMock).toHaveBeenCalledTimes(1);
expect(waitForTransportReadyMock).toHaveBeenCalledWith(
expect.objectContaining({
label: "signal daemon",
timeoutMs: 30_000,
logAfterMs: 10_000,
logIntervalMs: 10_000,
pollIntervalMs: 150,
runtime,
abortSignal: expect.any(AbortSignal),
}),
);
});
it("uses startupTimeoutMs override when provided", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig({ startupTimeoutMs: 60_000 });
const abortController = createAutoAbortController();
await runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
abortSignal: abortController.signal,
runtime,
startupTimeoutMs: 90_000,
});
expectWaitForTransportReadyTimeout(90_000);
});
it("caps startupTimeoutMs at 2 minutes", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig({ startupTimeoutMs: 180_000 });
const abortController = createAutoAbortController();
await runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
abortSignal: abortController.signal,
runtime,
});
expectWaitForTransportReadyTimeout(120_000);
});
it("fails fast when auto-started signal daemon exits during startup", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
spawnSignalDaemonMock.mockReturnValueOnce(
createMockSignalDaemonHandle({
exited: Promise.resolve({ source: "process", code: 1, signal: null }),
isExited: () => true,
}),
);
waitForTransportReadyMock.mockImplementationOnce(
async (params: { abortSignal?: AbortSignal | null }) => {
await new Promise<void>((_resolve, reject) => {
if (params.abortSignal?.aborted) {
reject(params.abortSignal.reason);
return;
}
params.abortSignal?.addEventListener(
"abort",
() => reject(params.abortSignal?.reason ?? new Error("aborted")),
{ once: true },
);
});
},
);
await expect(
runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
runtime,
}),
).rejects.toThrow(/signal daemon exited/i);
});
it("treats daemon exit after user abort as clean shutdown", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
const abortController = new AbortController();
let exited = false;
let resolveExit!: (value: SignalDaemonExitEvent) => void;
const exitedPromise = new Promise<SignalDaemonExitEvent>((resolve) => {
resolveExit = resolve;
});
const stop = vi.fn(() => {
if (exited) {
return;
}
exited = true;
resolveExit({ source: "process", code: null, signal: "SIGTERM" });
});
spawnSignalDaemonMock.mockReturnValueOnce(
createMockSignalDaemonHandle({
stop,
exited: exitedPromise,
isExited: () => exited,
}),
);
streamMock.mockImplementationOnce(async () => {
abortController.abort(new Error("stop"));
});
await expect(
runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
runtime,
abortSignal: abortController.signal,
}),
).resolves.toBeUndefined();
});
it("skips tool summaries with responsePrefix", async () => {
replyMock.mockResolvedValue({ text: "final reply" });

View File

@@ -51,6 +51,7 @@ const createTelegramBot = (opts: Parameters<typeof createTelegramBotBase>[0]) =>
});
const loadConfig = getLoadConfigMock();
const loadWebMedia = getLoadWebMediaMock();
const readChannelAllowFromStore = getReadChannelAllowFromStoreMock();
const upsertChannelPairingRequest = getUpsertChannelPairingRequestMock();
@@ -1259,7 +1260,6 @@ describe("createTelegramBot", () => {
});
it("sends GIF replies as animations", async () => {
const loadWebMedia = getLoadWebMediaMock();
replySpy.mockResolvedValueOnce({
text: "caption",
mediaUrl: "https://example.com/fun",
@@ -1269,7 +1269,6 @@ describe("createTelegramBot", () => {
contentType: "image/gif",
fileName: "fun.gif",
});
createTelegramBot({ token: "tok" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
@@ -1292,6 +1291,8 @@ describe("createTelegramBot", () => {
reply_to_message_id: undefined,
});
expect(sendPhotoSpy).not.toHaveBeenCalled();
expect(loadWebMedia).toHaveBeenCalledTimes(1);
expect(loadWebMedia.mock.calls[0]?.[0]).toBe("https://example.com/fun");
});
function resetHarnessSpies() {