test: speed up telegram extension suites

This commit is contained in:
Peter Steinberger
2026-03-24 15:55:40 +00:00
parent a29b9f2c20
commit 86921b624c
11 changed files with 64 additions and 30 deletions

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const ensureConfiguredBindingRouteReadyMock = vi.hoisted(() => vi.fn());
const resolveConfiguredBindingRouteMock = vi.hoisted(() => vi.fn());
@@ -128,14 +128,17 @@ function createConfiguredTelegramRoute() {
}
describe("buildTelegramMessageContext ACP configured bindings", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ buildTelegramMessageContextForTest } =
await import("./bot-message-context.test-harness.js"));
});
beforeEach(() => {
ensureConfiguredBindingRouteReadyMock.mockReset();
resolveConfiguredBindingRouteMock.mockReset();
resolveConfiguredBindingRouteMock.mockReturnValue(createConfiguredTelegramRoute());
ensureConfiguredBindingRouteReadyMock.mockResolvedValue({ ok: true });
({ buildTelegramMessageContextForTest } =
await import("./bot-message-context.test-harness.js"));
});
it("treats configured topic bindings as explicit route matches on non-default accounts", async () => {

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const transcribeFirstAudioMock = vi.fn();
const DEFAULT_MODEL = "anthropic/claude-opus-4-5";
@@ -75,13 +75,16 @@ function expectAudioPlaceholderRendered(ctx: Awaited<ReturnType<typeof buildGrou
}
describe("buildTelegramMessageContext audio transcript body", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
transcribeFirstAudioMock.mockReset();
({ buildTelegramMessageContextForTest } =
await import("./bot-message-context.test-harness.js"));
});
beforeEach(() => {
transcribeFirstAudioMock.mockReset();
});
it("uses preflight transcript as BodyForAgent for mention-gated group voice messages", async () => {
transcribeFirstAudioMock.mockResolvedValueOnce("hey bot please help");

View File

@@ -1,14 +1,17 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest;
let clearRuntimeConfigSnapshot: typeof import("../../../src/config/config.js").clearRuntimeConfigSnapshot;
let setRuntimeConfigSnapshot: typeof import("../../../src/config/config.js").setRuntimeConfigSnapshot;
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ buildTelegramMessageContextForTest } = await import("./bot-message-context.test-harness.js"));
({ clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } =
await import("../../../src/config/config.js"));
});
beforeEach(() => {
clearRuntimeConfigSnapshot();
});

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";
// Mock recordInboundSession to capture updateLastRoute parameter
const recordInboundSessionMock = vi.fn().mockResolvedValue(undefined);
@@ -36,13 +36,17 @@ describe("buildTelegramMessageContext DM topic threadId in deliveryContext (#889
recordInboundSessionMock.mockClear();
});
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ clearRuntimeConfigSnapshot } = await import("../../../src/config/config.js"));
({ buildTelegramMessageContextForTest } =
await import("./bot-message-context.test-harness.js"));
});
beforeEach(() => {
recordInboundSessionMock.mockClear();
});
it("passes threadId to updateLastRoute for DM topics", async () => {
const ctx = await buildCtx({
message: {

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 recordInboundSessionMock = vi.fn().mockResolvedValue(undefined);
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
@@ -25,7 +25,7 @@ describe("buildTelegramMessageContext named-account DM fallback", () => {
recordInboundSessionMock.mockClear();
});
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } =
await import("../../../src/config/config.js"));
@@ -33,6 +33,10 @@ describe("buildTelegramMessageContext named-account DM fallback", () => {
await import("./bot-message-context.test-harness.js"));
});
beforeEach(() => {
recordInboundSessionMock.mockClear();
});
function getLastUpdateLastRoute(): { sessionKey?: string } | undefined {
const callArgs = recordInboundSessionMock.mock.calls.at(-1)?.[0] as {
updateLastRoute?: { sessionKey?: 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 hoisted = vi.hoisted(() => {
const resolveByConversationMock = vi.fn();
@@ -27,14 +27,17 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest;
describe("buildTelegramMessageContext bound conversation override", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
hoisted.resolveByConversationMock.mockReset().mockReturnValue(null);
hoisted.touchMock.mockReset();
({ buildTelegramMessageContextForTest } =
await import("./bot-message-context.test-harness.js"));
});
beforeEach(() => {
hoisted.resolveByConversationMock.mockReset().mockReturnValue(null);
hoisted.touchMock.mockReset();
});
it("routes forum topic messages to the bound session", async () => {
hoisted.resolveByConversationMock.mockReturnValue({
bindingId: "default:-100200300:topic:77",

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 "../../../src/config/config.js";
const deliveryMocks = vi.hoisted(() => ({
deliverReplies: vi.fn(async () => ({ delivered: true })),
@@ -45,7 +45,7 @@ async function registerPairMenu(params: {
}
describe("registerTelegramNativeCommands real plugin registry", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
vi.doMock("./bot/delivery.js", () => ({
deliverReplies: deliveryMocks.deliverReplies,
@@ -62,6 +62,9 @@ describe("registerTelegramNativeCommands real plugin registry", () => {
createPrivateCommandContext,
waitForRegisteredCommands,
} = await import("./bot-native-commands.menu-test-support.js"));
});
beforeEach(() => {
clearPluginCommands();
deliveryMocks.deliverReplies.mockClear();
deliveryMocks.deliverReplies.mockResolvedValue({ delivered: 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 "../../../src/config/config.js";
import type { ResolvedAgentRoute } from "../../../src/routing/resolve-route.js";
import type { TelegramBotDeps } from "./bot-deps.js";
@@ -389,9 +389,12 @@ function expectUnauthorizedNewCommandBlocked(sendMessage: ReturnType<typeof vi.f
}
describe("registerTelegramNativeCommands — session metadata", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ registerTelegramNativeCommands } = await import("./bot-native-commands.js"));
});
beforeEach(() => {
persistentBindingMocks.resolveConfiguredBindingRoute.mockClear();
persistentBindingMocks.resolveConfiguredBindingRoute.mockImplementation(({ route }) =>
createConfiguredBindingRoute(route, null),

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 "../../../src/config/config.js";
import { STATE_DIR } from "../../../src/config/paths.js";
import { TELEGRAM_COMMAND_NAME_PATTERN } from "../../../src/config/telegram-custom-commands.js";
@@ -86,9 +86,12 @@ function resolveDeliverRepliesCalls() {
}
describe("registerTelegramNativeCommands", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ registerTelegramNativeCommands } = await import("./bot-native-commands.js"));
});
beforeEach(() => {
skillCommandMocks.listSkillCommandsForAgents.mockClear();
skillCommandMocks.listSkillCommandsForAgents.mockReturnValue([]);
deliveryMocks.deliverReplies.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 { botApi, botCtorSpy } = vi.hoisted(() => ({
botApi: {
@@ -89,7 +89,7 @@ describe("telegram proxy client", () => {
);
};
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({
deleteMessageTelegram,
@@ -97,6 +97,9 @@ describe("telegram proxy client", () => {
resetTelegramClientOptionsCacheForTests,
sendMessageTelegram,
} = await import("./send.js"));
});
beforeEach(() => {
resetTelegramClientOptionsCacheForTests();
vi.unstubAllEnvs();
botApi.sendMessage.mockResolvedValue({ message_id: 1, chat: { id: "123" } });

View File

@@ -22,6 +22,8 @@ const WEBHOOK_POST_TIMEOUT_MS = process.platform === "win32" ? 20_000 : 8_000;
const TELEGRAM_TOKEN = "tok";
const TELEGRAM_SECRET = "secret";
const TELEGRAM_WEBHOOK_PATH = "/hook";
const WEBHOOK_TEST_YIELD_MS = 0;
const WEBHOOK_DRAIN_GUARD_MS = 5;
function collectResponseBody(
res: IncomingMessage,
@@ -263,12 +265,12 @@ async function postWebhookPayloadWithChunkPlan(params: {
bytesQueued = offset;
chunksQueued += 1;
if (chunksQueued % 10 === 0) {
await sleep(1 + Math.floor(rng() * 3));
await sleep(WEBHOOK_TEST_YIELD_MS);
}
if (!canContinue) {
// Windows CI occasionally stalls on waiting for drain indefinitely.
// Bound the wait, then continue queuing this small (~1MB) payload.
await Promise.race([once(req, "drain"), sleep(25)]);
await Promise.race([once(req, "drain"), sleep(WEBHOOK_DRAIN_GUARD_MS)]);
}
}
phase = "awaiting-response";
@@ -568,7 +570,7 @@ describe("startTelegramWebhook", () => {
it("keeps webhook payload readable when callback delays body read", async () => {
handlerSpy.mockImplementationOnce(async (...args: unknown[]) => {
const [update, reply] = args as [unknown, (json: string) => Promise<void>];
await sleep(10);
await sleep(WEBHOOK_TEST_YIELD_MS);
await reply(JSON.stringify(update));
});
@@ -595,7 +597,7 @@ describe("startTelegramWebhook", () => {
const seenPayloads: string[] = [];
const delayedHandler = async (...args: unknown[]) => {
const [update, reply] = args as [unknown, (json: string) => Promise<void>];
await sleep(10);
await sleep(WEBHOOK_TEST_YIELD_MS);
seenPayloads.push(JSON.stringify(update));
await reply("ok");
};
@@ -639,7 +641,7 @@ describe("startTelegramWebhook", () => {
) => {
seenUpdates.push(update);
void (async () => {
await sleep(10);
await sleep(WEBHOOK_TEST_YIELD_MS);
await reply("ok");
})();
},