From f5f9a09e372f35ebe20f9bb1e67abe4c408d7fe6 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Thu, 4 Jun 2026 20:25:17 -0700 Subject: [PATCH] test: stabilize maintainer prepare baseline --- .../src/monitor/message-handler/prepare.ts | 4 +- ...te-telegram-bot.channel-post-media.test.ts | 83 +++++++++++++++++-- src/node-host/invoke.test.ts | 8 +- test/scripts/e2e-temp-state-dir.test.ts | 2 +- test/scripts/package-mac-app.test.ts | 6 +- 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/extensions/slack/src/monitor/message-handler/prepare.ts b/extensions/slack/src/monitor/message-handler/prepare.ts index 2c4edbd256d5..812ae3ad90a1 100644 --- a/extensions/slack/src/monitor/message-handler/prepare.ts +++ b/extensions/slack/src/monitor/message-handler/prepare.ts @@ -83,8 +83,8 @@ const SLACK_SUBTEAM_MENTION_RE = /|]+)(?:\|[^>]+)?>/g; const SLACK_SUBTEAM_MENTION_MARKER = " | undefined, diff --git a/extensions/telegram/src/bot.create-telegram-bot.channel-post-media.test.ts b/extensions/telegram/src/bot.create-telegram-bot.channel-post-media.test.ts index 3c70781566cb..bfad3412ca47 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.channel-post-media.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.channel-post-media.test.ts @@ -124,6 +124,53 @@ function resolveFlushTimerForDelay(setTimeoutSpy: ReturnType, d return flushTimer; } +type ScheduledTimer = { + callback: () => unknown; + handle: ReturnType; +}; + +function resolveActiveScheduledTimersForDelay( + setTimeoutSpy: ReturnType, + clearTimeoutSpy: ReturnType, + delayMs: number, +): ScheduledTimer[] { + const clearedHandles = new Set( + (clearTimeoutSpy.mock.calls as Array>).map( + ([handle]) => handle, + ), + ); + return (setTimeoutSpy.mock.calls as Array>).flatMap( + (call, index) => { + if (call[1] !== delayMs) { + return []; + } + const handle = setTimeoutSpy.mock.results[index]?.value as ReturnType; + if (clearedHandles.has(handle) || typeof call[0] !== "function") { + return []; + } + return [{ callback: call[0] as () => unknown, handle }]; + }, + ); +} + +async function flushActiveScheduledTimersForDelay(params: { + setTimeoutSpy: ReturnType; + clearTimeoutSpy: ReturnType; + delayMs: number; + expectedCount: number; +}) { + const timers = resolveActiveScheduledTimersForDelay( + params.setTimeoutSpy, + params.clearTimeoutSpy, + params.delayMs, + ); + expect(timers).toHaveLength(params.expectedCount); + for (const timer of timers) { + clearTimeout(timer.handle); + await timer.callback(); + } +} + function createImageFetchSpy(params?: { body?: Uint8Array; contentType?: string }) { return vi.spyOn(globalThis, "fetch").mockImplementation( async () => @@ -139,12 +186,13 @@ async function waitForBufferedProcessing() { } async function waitForMockCalls(mock: { mock: { calls: unknown[] } }, count: number) { - for (let index = 0; index < 80; index++) { + for (let index = 0; index < 400; index++) { if (mock.mock.calls.length >= count) { return; } await delay(25); } + throw new Error(`Timed out waiting for ${count} mock call(s); got ${mock.mock.calls.length}`); } function createChannelPostContext(params: { @@ -271,8 +319,13 @@ describe("createTelegramBot channel_post media", () => { setOpenChannelPostConfig(); const fetchSpy = createImageFetchSpy(); - - const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + let nextTimerHandle = 1; + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout").mockImplementation(() => { + const handle = nextTimerHandle; + nextTimerHandle += 1; + return handle as unknown as ReturnType; + }); + const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout"); try { const handler = getChannelPostHandler(); await queueChannelPostAlbum(handler, { @@ -282,7 +335,12 @@ describe("createTelegramBot channel_post media", () => { secondMessageId: 202, }); expect(replySpy).not.toHaveBeenCalled(); - await flushChannelPostMediaGroup(setTimeoutSpy); + await flushActiveScheduledTimersForDelay({ + setTimeoutSpy, + clearTimeoutSpy, + delayMs: TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs, + expectedCount: 1, + }); await waitForMockCalls(replySpy, 1); await vi.waitFor(() => expect(replySpy).toHaveBeenCalledTimes(1)); @@ -290,6 +348,7 @@ describe("createTelegramBot channel_post media", () => { expect(payload.Body).toContain("album caption"); } finally { setTimeoutSpy.mockRestore(); + clearTimeoutSpy.mockRestore(); fetchSpy.mockRestore(); } }); @@ -311,7 +370,13 @@ describe("createTelegramBot channel_post media", () => { }); const fetchSpy = createImageFetchSpy(); - const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + let nextTimerHandle = 1; + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout").mockImplementation(() => { + const handle = nextTimerHandle; + nextTimerHandle += 1; + return handle as unknown as ReturnType; + }); + const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout"); try { const handler = getChannelPostHandlerWithRuntimeTimings(); await queueChannelPostAlbum(handler, { @@ -321,7 +386,12 @@ describe("createTelegramBot channel_post media", () => { secondMessageId: 212, }); expect(replySpy).not.toHaveBeenCalled(); - await flushChannelPostMediaGroupForDelay(setTimeoutSpy, 75); + await flushActiveScheduledTimersForDelay({ + setTimeoutSpy, + clearTimeoutSpy, + delayMs: 75, + expectedCount: 1, + }); await waitForMockCalls(replySpy, 1); await vi.waitFor(() => expect(replySpy).toHaveBeenCalledTimes(1)); @@ -329,6 +399,7 @@ describe("createTelegramBot channel_post media", () => { expect(payload.Body).toContain("configured album"); } finally { setTimeoutSpy.mockRestore(); + clearTimeoutSpy.mockRestore(); fetchSpy.mockRestore(); } }); diff --git a/src/node-host/invoke.test.ts b/src/node-host/invoke.test.ts index 7343aa7a0f89..6db0b502e456 100644 --- a/src/node-host/invoke.test.ts +++ b/src/node-host/invoke.test.ts @@ -10,7 +10,7 @@ import { handleInvoke } from "./invoke.js"; describe("node host invoke", () => { it.runIf(process.platform !== "win32")( - "reports current allow-always coverage for prepared shell-wrapped system.run commands", + "reports current allow-always coverage for prepared system.run commands", async () => { const request = vi.fn().mockResolvedValue(null); const skillBins: SkillBinsProvider = { current: async () => [] }; @@ -21,7 +21,7 @@ describe("node host invoke", () => { nodeId: "node-1", command: "system.run.prepare", paramsJSON: JSON.stringify({ - command: ["/bin/sh", "-lc", "/bin/echo ok"], + command: ["/bin/echo", "ok"], rawCommand: "/bin/echo ok", }), }, @@ -29,7 +29,9 @@ describe("node host invoke", () => { skillBins, ); - const result = request.mock.calls[0]?.[1] as { payloadJSON?: string } | undefined; + const result = request.mock.calls.find(([method]) => method === "node.invoke.result")?.[1] as + | { payloadJSON?: string } + | undefined; const payload = JSON.parse(result?.payloadJSON ?? "{}") as { allowAlwaysCoverage?: { complete?: boolean; diff --git a/test/scripts/e2e-temp-state-dir.test.ts b/test/scripts/e2e-temp-state-dir.test.ts index df69472eec6b..328c170c6448 100644 --- a/test/scripts/e2e-temp-state-dir.test.ts +++ b/test/scripts/e2e-temp-state-dir.test.ts @@ -60,7 +60,7 @@ describe("E2E temp state dirs", () => { } }); - it.runIf(process.platform !== "win32")( + it.runIf(process.platform !== "win32" && process.getuid?.() !== 0)( "retries generated state cleanup after a failed removal", async () => { const root = mkdtempSync(path.join(tmpdir(), "openclaw-e2e-temp-state-retry-")); diff --git a/test/scripts/package-mac-app.test.ts b/test/scripts/package-mac-app.test.ts index fd3908fdca5e..77798bf2abbd 100644 --- a/test/scripts/package-mac-app.test.ts +++ b/test/scripts/package-mac-app.test.ts @@ -78,7 +78,7 @@ describe("package-mac-app plist stamping", () => { writeFileSync( corepackPath, [ - "#!/usr/bin/env bash", + "#!/bin/bash", "set -euo pipefail", 'printf \'%s|%s\\n\' "$PWD" "$*" >> "$OPENCLAW_TEST_LOG"', 'if [[ "${1:-}" == "pnpm" && "${2:-}" == "--version" ]]; then', @@ -95,7 +95,7 @@ describe("package-mac-app plist stamping", () => { ROOT_DIR=${JSON.stringify(tempRoot)} OPENCLAW_TEST_LOG=${JSON.stringify(logPath)} export OPENCLAW_TEST_LOG - PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)} + PATH=${JSON.stringify(toolsDir)} ${helperBlock} run_pnpm install --frozen-lockfile --config.node-linker=hoisted run_pnpm build @@ -118,7 +118,7 @@ describe("package-mac-app plist stamping", () => { const result = runHelper(` set -euo pipefail ROOT_DIR=${JSON.stringify(tempRoot)} - PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)} + PATH=${JSON.stringify(toolsDir)} ${helperBlock} run_pnpm build `);