From e4841d767d1416c66deb5c136fba08805deff2d8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Apr 2026 11:52:43 -0700 Subject: [PATCH] test: stabilize loaded full-suite checks --- .../test-support/lifecycle-test-support.ts | 14 +++++++---- .../qa-lab/src/manual-lane.runtime.test.ts | 6 +++++ .../qa-lab/src/multipass.runtime.test.ts | 8 +++++-- extensions/telegram/contract-api.ts | 4 ++++ src/cli/config-cli.integration.test.ts | 4 ++-- .../runtime-live-state-guardrails.test.ts | 4 ++-- .../plugins-core-extension-contract.ts | 17 +++++++------ ...eck-extension-package-tsc-boundary.test.ts | 24 ++++++++++--------- ...tension-package-boundary-artifacts.test.ts | 14 ++++++----- 9 files changed, 61 insertions(+), 34 deletions(-) diff --git a/extensions/feishu/src/test-support/lifecycle-test-support.ts b/extensions/feishu/src/test-support/lifecycle-test-support.ts index 389767b8c2b..6110335b059 100644 --- a/extensions/feishu/src/test-support/lifecycle-test-support.ts +++ b/extensions/feishu/src/test-support/lifecycle-test-support.ts @@ -5,6 +5,8 @@ import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../../runtime-ap import { setFeishuRuntime } from "../runtime.js"; import type { ResolvedFeishuAccount } from "../types.js"; +const FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS = 10_000; + type InboundDebouncerParams = { onFlush?: (items: T[]) => Promise; onError?: (err: unknown, items: T[]) => void; @@ -303,9 +305,11 @@ export async function replayFeishuLifecycleEvent(params: { waitForSecond?: () => void | Promise; }) { await params.handler(params.event); - await vi.waitFor(params.waitForFirst); + await vi.waitFor(params.waitForFirst, { timeout: FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS }); await params.handler(params.event); - await vi.waitFor(params.waitForSecond ?? params.waitForFirst); + await vi.waitFor(params.waitForSecond ?? params.waitForFirst, { + timeout: FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS, + }); } export async function runFeishuLifecycleSequence( @@ -314,7 +318,9 @@ export async function runFeishuLifecycleSequence( ) { for (const [index, deliver] of deliveries.entries()) { await deliver(); - await vi.waitFor(waits[index] ?? waits.at(-1) ?? (() => {})); + await vi.waitFor(waits[index] ?? waits.at(-1) ?? (() => {}), { + timeout: FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS, + }); } } @@ -341,7 +347,7 @@ export async function expectFeishuReplyPipelineDedupedAcrossReplay(params: { createFeishuReplyDispatcherMock: ReturnType; waitTimeoutMs?: number; }) { - const waitTimeoutMs = params.waitTimeoutMs; + const waitTimeoutMs = params.waitTimeoutMs ?? FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS; await replayFeishuLifecycleEvent({ handler: params.handler, event: params.event, diff --git a/extensions/qa-lab/src/manual-lane.runtime.test.ts b/extensions/qa-lab/src/manual-lane.runtime.test.ts index 1c99082f19c..f8858a08058 100644 --- a/extensions/qa-lab/src/manual-lane.runtime.test.ts +++ b/extensions/qa-lab/src/manual-lane.runtime.test.ts @@ -37,6 +37,12 @@ describe("runQaManualLane", () => { listenUrl: "http://127.0.0.1:43124", baseUrl: "http://127.0.0.1:58000", state: { + reset: vi.fn(), + addInboundMessage: vi.fn(), + addOutboundMessage: vi.fn(), + readMessage: vi.fn(), + searchMessages: vi.fn(() => []), + waitFor: vi.fn(), getSnapshot: () => ({ messages: [ { diff --git a/extensions/qa-lab/src/multipass.runtime.test.ts b/extensions/qa-lab/src/multipass.runtime.test.ts index 17b16280205..05273074cca 100644 --- a/extensions/qa-lab/src/multipass.runtime.test.ts +++ b/extensions/qa-lab/src/multipass.runtime.test.ts @@ -93,7 +93,9 @@ describe("qa multipass runtime", () => { expect(script).toContain("pnpm install --frozen-lockfile"); expect(script).toContain("pnpm build"); expect(script).toContain("corepack prepare 'pnpm@10.32.1' --activate"); - expect(script).toContain("'pnpm' 'openclaw' 'qa' 'suite' '--provider-mode' 'mock-openai'"); + expect(script).toContain( + "'pnpm' 'openclaw' 'qa' 'suite' '--transport' 'qa-channel' '--provider-mode' 'mock-openai'", + ); expect(script).toContain("'--scenario' 'channel-chat-baseline'"); expect(script).toContain("'--scenario' 'thread-follow-up'"); expect(script).toContain("/workspace/openclaw-host/.artifacts/qa-e2e/multipass-test"); @@ -126,7 +128,9 @@ describe("qa multipass runtime", () => { ); expect(plan.forwardedEnv.OPENAI_API_KEY).toBe("test-openai-key"); expect(script).toContain("OPENAI_API_KEY='test-openai-key'"); - expect(script).toContain("'pnpm' 'openclaw' 'qa' 'suite' '--provider-mode' 'live-frontier'"); + expect(script).toContain( + "'pnpm' 'openclaw' 'qa' 'suite' '--transport' 'qa-channel' '--provider-mode' 'live-frontier'", + ); }); it("redacts forwarded live secrets in the persisted artifact script", () => { diff --git a/extensions/telegram/contract-api.ts b/extensions/telegram/contract-api.ts index 9f0d3e362b2..6b6b454ad74 100644 --- a/extensions/telegram/contract-api.ts +++ b/extensions/telegram/contract-api.ts @@ -9,6 +9,10 @@ export { normalizeTelegramCommandName, resolveTelegramCustomCommands, } from "./src/command-config.js"; +export { + listTelegramDirectoryGroupsFromConfig, + listTelegramDirectoryPeersFromConfig, +} from "./src/directory-config.js"; export { parseTelegramTopicConversation } from "./src/topic-conversation.js"; export { singleAccountKeysToMove } from "./src/setup-contract.js"; export { mergeTelegramAccountConfig } from "./src/accounts.js"; diff --git a/src/cli/config-cli.integration.test.ts b/src/cli/config-cli.integration.test.ts index 7462459ddfe..22656400643 100644 --- a/src/cli/config-cli.integration.test.ts +++ b/src/cli/config-cli.integration.test.ts @@ -43,8 +43,8 @@ function createExecDryRunBatch(params: { markerPath: string }) { command: process.execPath, args: ["-e", script], allowInsecurePath: true, - timeoutMs: 15_000, - noOutputTimeoutMs: 15_000, + timeoutMs: 60_000, + noOutputTimeoutMs: 60_000, }, }, { diff --git a/src/plugins/runtime-live-state-guardrails.test.ts b/src/plugins/runtime-live-state-guardrails.test.ts index c239bd022da..9c75d8240b6 100644 --- a/src/plugins/runtime-live-state-guardrails.test.ts +++ b/src/plugins/runtime-live-state-guardrails.test.ts @@ -13,8 +13,8 @@ const LIVE_RUNTIME_STATE_GUARDS: Record< forbidden: readonly string[]; } > = { - [bundledPluginFile("whatsapp", "src/active-listener.ts")]: { - required: ["globalThis", 'Symbol.for("openclaw.whatsapp.activeListenerState")'], + [bundledPluginFile("whatsapp", "src/connection-controller-registry.ts")]: { + required: ["globalThis", 'Symbol.for("openclaw.whatsapp.connectionControllerRegistry")'], forbidden: ["resolveGlobalSingleton"], }, }; diff --git a/test/helpers/channels/plugins-core-extension-contract.ts b/test/helpers/channels/plugins-core-extension-contract.ts index 5c0aec6a6c7..4604ad4cfd6 100644 --- a/test/helpers/channels/plugins-core-extension-contract.ts +++ b/test/helpers/channels/plugins-core-extension-contract.ts @@ -25,14 +25,17 @@ type SlackContractApiSurface = Pick< "listSlackDirectoryPeersFromConfig" | "listSlackDirectoryGroupsFromConfig" >; type SlackProbe = import("@openclaw/slack/api.js").SlackProbe; -type TelegramApiSurface = typeof import("@openclaw/telegram/api.js"); +type TelegramContractApiSurface = Pick< + typeof import("@openclaw/telegram/contract-api.js"), + "listTelegramDirectoryPeersFromConfig" | "listTelegramDirectoryGroupsFromConfig" +>; type TelegramProbe = import("@openclaw/telegram/api.js").TelegramProbe; type TelegramTokenResolution = import("@openclaw/telegram/api.js").TelegramTokenResolution; type WhatsAppApiSurface = typeof import("@openclaw/whatsapp/api.js"); let discordContractApi: DiscordContractApiSurface | undefined; let slackContractApi: SlackContractApiSurface | undefined; -let telegramApi: TelegramApiSurface | undefined; +let telegramContractApi: TelegramContractApiSurface | undefined; let whatsappApi: WhatsAppApiSurface | undefined; function getDiscordContractApi(): DiscordContractApiSurface { @@ -45,9 +48,9 @@ function getSlackContractApi(): SlackContractApiSurface { return slackContractApi; } -function getTelegramApi(): TelegramApiSurface { - telegramApi ??= loadBundledPluginApiSync("telegram"); - return telegramApi; +function getTelegramContractApi(): TelegramContractApiSurface { + telegramContractApi ??= loadBundledPluginContractApiSync("telegram"); + return telegramContractApi; } function getWhatsAppApi(): WhatsAppApiSurface { @@ -261,8 +264,8 @@ export function describeSlackPluginsCoreExtensionContract() { export function describeTelegramPluginsCoreExtensionContract() { describe("telegram plugins-core extension contract", () => { - const listPeers = () => getTelegramApi().listTelegramDirectoryPeersFromConfig; - const listGroups = () => getTelegramApi().listTelegramDirectoryGroupsFromConfig; + const listPeers = () => getTelegramContractApi().listTelegramDirectoryPeersFromConfig; + const listGroups = () => getTelegramContractApi().listTelegramDirectoryGroupsFromConfig; it("TelegramProbe satisfies BaseProbeResult", () => { expectTypeOf().toMatchTypeOf(); diff --git a/test/scripts/check-extension-package-tsc-boundary.test.ts b/test/scripts/check-extension-package-tsc-boundary.test.ts index 597860d96d7..dddaa5190fd 100644 --- a/test/scripts/check-extension-package-tsc-boundary.test.ts +++ b/test/scripts/check-extension-package-tsc-boundary.test.ts @@ -312,7 +312,7 @@ describe("check-extension-package-tsc-boundary", () => { "process.exit(2);", ].join(" "), ], - 5_000, + 20_000, ), ).rejects.toMatchObject({ message: expect.stringContaining("[... 6 earlier lines omitted ...]"), @@ -320,31 +320,33 @@ describe("check-extension-package-tsc-boundary", () => { kind: "nonzero-exit", elapsedMs: expect.any(Number), }); - }); + }, 30_000); it("aborts concurrent sibling steps after the first failure", async () => { const startedAt = Date.now(); + const slowStepTimeoutMs = 60_000; + const abortBudgetMs = 30_000; await expect( runNodeStepsWithConcurrency( [ { label: "fail-fast", - args: ["--eval", "setTimeout(() => process.exit(2), 10)"], - timeoutMs: 5_000, + args: ["--eval", "process.exit(2)"], + timeoutMs: slowStepTimeoutMs, }, { label: "slow-step", - args: ["--eval", "setTimeout(() => {}, 10_000)"], - timeoutMs: 5_000, + args: ["--eval", "setTimeout(() => {}, 60_000)"], + timeoutMs: slowStepTimeoutMs, }, ], 2, ), ).rejects.toThrow("fail-fast"); - expect(Date.now() - startedAt).toBeLessThan(2_000); - }); + expect(Date.now() - startedAt).toBeLessThan(abortBudgetMs); + }, 45_000); it("passes successful step timing metadata to onSuccess handlers", async () => { const elapsedTimes: number[] = []; @@ -353,8 +355,8 @@ describe("check-extension-package-tsc-boundary", () => { [ { label: "demo-step", - args: ["--eval", "setTimeout(() => process.exit(0), 10)"], - timeoutMs: 5_000, + args: ["--eval", "process.exit(0)"], + timeoutMs: 20_000, onSuccess(result: { elapsedMs: number }) { elapsedTimes.push(result.elapsedMs); }, @@ -365,5 +367,5 @@ describe("check-extension-package-tsc-boundary", () => { expect(elapsedTimes).toHaveLength(1); expect(elapsedTimes[0]).toBeGreaterThanOrEqual(0); - }); + }, 30_000); }); diff --git a/test/scripts/prepare-extension-package-boundary-artifacts.test.ts b/test/scripts/prepare-extension-package-boundary-artifacts.test.ts index acf75b650ad..95e5e12aa86 100644 --- a/test/scripts/prepare-extension-package-boundary-artifacts.test.ts +++ b/test/scripts/prepare-extension-package-boundary-artifacts.test.ts @@ -36,24 +36,26 @@ describe("prepare-extension-package-boundary-artifacts", () => { it("aborts sibling steps after the first failure", async () => { const startedAt = Date.now(); + const slowStepTimeoutMs = 60_000; + const abortBudgetMs = 30_000; await expect( runNodeStepsInParallel([ { label: "fail-fast", - args: ["--eval", "setTimeout(() => process.exit(2), 10)"], - timeoutMs: 5_000, + args: ["--eval", "process.exit(2)"], + timeoutMs: slowStepTimeoutMs, }, { label: "slow-step", - args: ["--eval", "setTimeout(() => {}, 10_000)"], - timeoutMs: 5_000, + args: ["--eval", "setTimeout(() => {}, 60_000)"], + timeoutMs: slowStepTimeoutMs, }, ]), ).rejects.toThrow("fail-fast failed with exit code 2"); - expect(Date.now() - startedAt).toBeLessThan(2_000); - }); + expect(Date.now() - startedAt).toBeLessThan(abortBudgetMs); + }, 45_000); it("treats artifacts as fresh only when outputs are newer than inputs", () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-boundary-prep-"));