From 83bb6472385871937e8f8161b018d0dbb9709961 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 24 Mar 2026 15:14:45 +0000 Subject: [PATCH] test: speed up telegram extension suites --- .../telegram/src/bot-message-dispatch.test.ts | 7 +++-- extensions/telegram/src/bot-message.test.ts | 9 ++++-- .../src/bot.create-telegram-bot.test.ts | 21 ++++++++------ extensions/telegram/src/bot.test.ts | 22 ++++++++------- extensions/telegram/src/fetch.test.ts | 9 ++++-- extensions/telegram/src/monitor.test.ts | 28 +++++-------------- extensions/telegram/src/probe.test.ts | 4 +-- .../src/sendchataction-401-backoff.test.ts | 22 +++++++++++---- 8 files changed, 66 insertions(+), 56 deletions(-) diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index c78fb6e6223..37227943ce8 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -1,5 +1,5 @@ import type { Bot } from "grammy"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { TelegramBotDeps } from "./bot-deps.js"; import { createSequencedTestDraftStream, @@ -120,9 +120,12 @@ const telegramDepsForTest: TelegramBotDeps = { describe("dispatchTelegramMessage draft streaming", () => { type TelegramMessageContext = Parameters[0]["context"]; - beforeEach(async () => { + beforeAll(async () => { vi.resetModules(); ({ dispatchTelegramMessage } = await import("./bot-message-dispatch.js")); + }); + + beforeEach(() => { createTelegramDraftStream.mockClear(); dispatchReplyWithBufferedBlockDispatcher.mockClear(); deliverReplies.mockClear(); diff --git a/extensions/telegram/src/bot-message.test.ts b/extensions/telegram/src/bot-message.test.ts index d965daec06a..f3028fa4e27 100644 --- a/extensions/telegram/src/bot-message.test.ts +++ b/extensions/telegram/src/bot-message.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { TelegramBotDeps } from "./bot-deps.js"; const buildTelegramMessageContext = vi.hoisted(() => vi.fn()); @@ -18,12 +18,15 @@ vi.mock("./bot-message-dispatch.js", () => ({ let createTelegramMessageProcessor: typeof import("./bot-message.js").createTelegramMessageProcessor; describe("telegram bot message processor", () => { - beforeEach(async () => { + beforeAll(async () => { vi.resetModules(); + ({ createTelegramMessageProcessor } = await import("./bot-message.js")); + }); + + beforeEach(() => { buildTelegramMessageContext.mockClear(); dispatchTelegramMessage.mockClear(); upsertChannelPairingRequest.mockClear(); - ({ createTelegramMessageProcessor } = await import("./bot-message.js")); }); const telegramDepsForTest = { diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index 7752f05c708..54ccfa4e2aa 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -35,6 +35,8 @@ const { useSpy, } = harness; let resolveTelegramFetch: typeof import("./fetch.js").resolveTelegramFetch; +let setTelegramBotRuntimeForTest: typeof import("./bot.js").setTelegramBotRuntimeForTest; +let createTelegramBotBase: typeof import("./bot.js").createTelegramBot; let createTelegramBot: ( opts: Parameters[0], ) => ReturnType; @@ -79,21 +81,22 @@ describe("createTelegramBot", () => { beforeAll(() => { process.env.TZ = "UTC"; }); + beforeAll(async () => { + vi.resetModules(); + ({ resolveTelegramFetch } = await import("./fetch.js")); + ({ + createTelegramBot: createTelegramBotBase, + getTelegramSequentialKey, + setTelegramBotRuntimeForTest, + } = await import("./bot.js")); + }); afterAll(() => { process.env.TZ = ORIGINAL_TZ; }); - beforeEach(async () => { - vi.resetModules(); - ({ resolveTelegramFetch } = await import("./fetch.js")); - const { - createTelegramBot: createTelegramBotBase, - getTelegramSequentialKey: importedGetTelegramSequentialKey, - setTelegramBotRuntimeForTest, - } = await import("./bot.js"); + beforeEach(() => { setTelegramBotRuntimeForTest( telegramBotRuntimeForTest as unknown as Parameters[0], ); - getTelegramSequentialKey = importedGetTelegramSequentialKey; createTelegramBot = (opts) => createTelegramBotBase({ ...opts, diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index 5c2a67f2a50..c7866bcf53b 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -32,6 +32,8 @@ let listNativeCommandSpecs: typeof import("../../../src/auto-reply/commands-regi let listNativeCommandSpecsForConfig: typeof import("../../../src/auto-reply/commands-registry.js").listNativeCommandSpecsForConfig; let loadSessionStore: typeof import("../../../src/config/sessions.js").loadSessionStore; let normalizeTelegramCommandName: typeof import("../../../src/config/telegram-custom-commands.js").normalizeTelegramCommandName; +let createTelegramBotBase: typeof import("./bot.js").createTelegramBot; +let setTelegramBotRuntimeForTest: typeof import("./bot.js").setTelegramBotRuntimeForTest; let createTelegramBot: ( opts: Parameters[0], ) => ReturnType; @@ -48,6 +50,16 @@ function resolveSkillCommands(config: Parameters { + beforeAll(async () => { + vi.resetModules(); + ({ listNativeCommandSpecs, listNativeCommandSpecsForConfig } = + await import("../../../src/auto-reply/commands-registry.js")); + ({ loadSessionStore } = await import("../../../src/config/sessions.js")); + ({ normalizeTelegramCommandName } = + await import("../../../src/config/telegram-custom-commands.js")); + ({ createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } = + await import("./bot.js")); + }); beforeAll(() => { process.env.TZ = "UTC"; }); @@ -68,16 +80,6 @@ describe("createTelegramBot", () => { telegram: { dmPolicy: "open", allowFrom: ["*"] }, }, }); - }); - beforeEach(async () => { - vi.resetModules(); - ({ listNativeCommandSpecs, listNativeCommandSpecsForConfig } = - await import("../../../src/auto-reply/commands-registry.js")); - ({ loadSessionStore } = await import("../../../src/config/sessions.js")); - ({ normalizeTelegramCommandName } = - await import("../../../src/config/telegram-custom-commands.js")); - const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } = - await import("./bot.js"); setTelegramBotRuntimeForTest( telegramBotRuntimeForTest as unknown as Parameters[0], ); diff --git a/extensions/telegram/src/fetch.test.ts b/extensions/telegram/src/fetch.test.ts index 832a83f39a1..55f6696f907 100644 --- a/extensions/telegram/src/fetch.test.ts +++ b/extensions/telegram/src/fetch.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const setDefaultResultOrder = vi.hoisted(() => vi.fn()); const setDefaultAutoSelectFamily = vi.hoisted(() => vi.fn()); @@ -58,12 +58,16 @@ let resolveFetch: typeof import("../../../src/infra/fetch.js").resolveFetch; let resolveTelegramFetch: typeof import("./fetch.js").resolveTelegramFetch; let resolveTelegramTransport: typeof import("./fetch.js").resolveTelegramTransport; -beforeEach(async () => { +beforeAll(async () => { vi.resetModules(); ({ resolveFetch } = await import("../../../src/infra/fetch.js")); ({ resolveTelegramFetch, resolveTelegramTransport } = await import("./fetch.js")); }); +beforeEach(() => { + vi.unstubAllEnvs(); +}); + function resolveTelegramFetchOrThrow( proxyFetch?: typeof fetch, options?: { network?: { autoSelectFamily?: boolean; dnsResultOrder?: "ipv4first" | "verbatim" } }, @@ -218,7 +222,6 @@ afterEach(() => { ProxyAgentCtor.mockClear(); setDefaultResultOrder.mockReset(); setDefaultAutoSelectFamily.mockReset(); - vi.unstubAllEnvs(); vi.clearAllMocks(); }); diff --git a/extensions/telegram/src/monitor.test.ts b/extensions/telegram/src/monitor.test.ts index e978bb17972..5622e142dac 100644 --- a/extensions/telegram/src/monitor.test.ts +++ b/extensions/telegram/src/monitor.test.ts @@ -1,6 +1,7 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; type MonitorTelegramOpts = import("./monitor.js").MonitorTelegramOpts; +let monitorTelegramProvider: typeof import("./monitor.js").monitorTelegramProvider; type MockCtx = { message: { @@ -137,7 +138,6 @@ function mockRunOnceAndAbort(abort: AbortController) { } async function expectOffsetConfirmationSkipped(offset: number | null) { - const { monitorTelegramProvider } = await import("./monitor.js"); readTelegramUpdateOffsetSpy.mockResolvedValueOnce(offset); const abort = new AbortController(); api.getUpdates.mockReset(); @@ -151,7 +151,6 @@ async function expectOffsetConfirmationSkipped(offset: number | null) { } async function runMonitorAndCaptureStartupOrder(params?: { persistedOffset?: number | null }) { - const { monitorTelegramProvider } = await import("./monitor.js"); if (params && "persistedOffset" in params) { readTelegramUpdateOffsetSpy.mockResolvedValueOnce(params.persistedOffset ?? null); } @@ -233,7 +232,6 @@ function expectRecoverableRetryState( } async function monitorWithAutoAbort(opts: Omit = {}) { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); mockRunOnceAndAbort(abort); await monitorTelegramProvider({ @@ -337,8 +335,12 @@ vi.mock("../../../src/auto-reply/reply.js", () => ({ describe("monitorTelegramProvider (grammY)", () => { let consoleErrorSpy: { mockRestore: () => void } | undefined; - beforeEach(() => { + beforeAll(async () => { vi.resetModules(); + ({ monitorTelegramProvider } = await import("./monitor.js")); + }); + + beforeEach(() => { loadConfig.mockReturnValue({ agents: { defaults: { maxConcurrent: 2 } }, channels: { telegram: {} }, @@ -434,7 +436,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("retries on recoverable undici fetch errors", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const networkError = makeRecoverableFetchError(); runSpy @@ -458,7 +459,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("retries recoverable deleteWebhook failures before polling", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const cleanupError = makeRecoverableFetchError(); api.deleteWebhook.mockReset(); @@ -472,7 +472,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("retries setup-time recoverable errors before starting polling", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const setupError = makeRecoverableFetchError(); createTelegramBotErrors.push(setupError); @@ -484,7 +483,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("awaits runner.stop before retrying after recoverable polling error", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const recoverableError = makeRecoverableFetchError(); let firstStopped = false; @@ -512,7 +510,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("stops bot instance when polling cycle exits", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); mockRunOnceAndAbort(abort); @@ -523,7 +520,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("clears bounded cleanup timers after a clean stop", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); vi.useFakeTimers(); try { const abort = new AbortController(); @@ -538,7 +534,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("surfaces non-recoverable errors", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); runSpy.mockImplementationOnce(() => makeRunnerStub({ task: () => Promise.reject(new Error("bad token")), @@ -549,7 +544,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("force-restarts polling when unhandled network rejection stalls runner", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const firstCycle = mockRunOnceWithStalledPollingRunner(); mockRunOnceWithStalledPollingRunner(); @@ -566,7 +560,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("reuses the resolved transport across polling restarts", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); vi.useFakeTimers({ shouldAdvanceTime: true }); try { const telegramTransport = { @@ -595,7 +588,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("aborts the active Telegram fetch when unhandled network rejection forces restart", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const { stop, waitForTaskStart } = mockRunOnceWithStalledPollingRunner(); mockRunOnceAndAbort(abort); @@ -615,7 +607,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("ignores unrelated process-level network errors while telegram polling is active", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const { stop } = mockRunOnceWithStalledPollingRunner(); @@ -641,7 +632,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("passes configured webhookHost to webhook listener", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); await monitorTelegramProvider({ token: "tok", useWebhook: true, @@ -666,7 +656,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("webhook mode waits for abort signal before returning", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); const settled = vi.fn(); const monitor = monitorTelegramProvider({ @@ -686,7 +675,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("force-restarts polling when getUpdates stalls (watchdog)", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); vi.useFakeTimers({ shouldAdvanceTime: true }); const abort = new AbortController(); const { stop } = mockRunOnceWithStalledPollingRunner(); @@ -726,7 +714,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("resets webhookCleared latch on 409 conflict so deleteWebhook re-runs", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); const abort = new AbortController(); api.deleteWebhook.mockReset(); api.deleteWebhook.mockResolvedValue(true); @@ -765,7 +752,6 @@ describe("monitorTelegramProvider (grammY)", () => { }); it("falls back to configured webhookSecret when not passed explicitly", async () => { - const { monitorTelegramProvider } = await import("./monitor.js"); await monitorTelegramProvider({ token: "tok", useWebhook: true, diff --git a/extensions/telegram/src/probe.test.ts b/extensions/telegram/src/probe.test.ts index 4f8536f3970..4b39f36f0ec 100644 --- a/extensions/telegram/src/probe.test.ts +++ b/extensions/telegram/src/probe.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, type Mock, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, type Mock, describe, expect, it, vi } from "vitest"; import { withFetchPreconnect } from "../../../test/helpers/extensions/fetch-mock.js"; const resolveTelegramFetch = vi.hoisted(() => vi.fn()); @@ -72,7 +72,7 @@ describe("probeTelegram retry logic", () => { } }); - beforeEach(async () => { + beforeAll(async () => { vi.resetModules(); ({ probeTelegram, resetTelegramProbeFetcherCacheForTests } = await import("./probe.js")); }); diff --git a/extensions/telegram/src/sendchataction-401-backoff.test.ts b/extensions/telegram/src/sendchataction-401-backoff.test.ts index 302a3b19c4d..3fb137972bc 100644 --- a/extensions/telegram/src/sendchataction-401-backoff.test.ts +++ b/extensions/telegram/src/sendchataction-401-backoff.test.ts @@ -1,16 +1,26 @@ -import { describe, expect, it, vi } from "vitest"; -import { createTelegramSendChatActionHandler } from "./sendchataction-401-backoff.js"; +import { beforeAll, describe, expect, it, vi } from "vitest"; -// Mock the backoff sleep to avoid real delays in tests -vi.mock("../../../src/infra/backoff.js", async (importOriginal) => { - const actual = await importOriginal(); +const mocks = vi.hoisted(() => ({ + sleepWithAbort: vi.fn().mockResolvedValue(undefined), +})); + +// Mock the runtime-exported backoff sleep that the handler actually imports. +vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, - sleepWithAbort: vi.fn().mockResolvedValue(undefined), + sleepWithAbort: mocks.sleepWithAbort, }; }); +let createTelegramSendChatActionHandler: typeof import("./sendchataction-401-backoff.js").createTelegramSendChatActionHandler; + describe("createTelegramSendChatActionHandler", () => { + beforeAll(async () => { + vi.resetModules(); + ({ createTelegramSendChatActionHandler } = await import("./sendchataction-401-backoff.js")); + }); + const make401Error = () => new Error("401 Unauthorized"); const make500Error = () => new Error("500 Internal Server Error");