test: stabilize maintainer prepare baseline

This commit is contained in:
Gio Della-Libera
2026-06-04 20:25:17 -07:00
parent efd1a9ace6
commit f5f9a09e37
5 changed files with 88 additions and 15 deletions

View File

@@ -83,8 +83,8 @@ const SLACK_SUBTEAM_MENTION_RE = /<!subteam\^([^>|]+)(?:\|[^>]+)?>/g;
const SLACK_SUBTEAM_MENTION_MARKER = "<!subteam^"; const SLACK_SUBTEAM_MENTION_MARKER = "<!subteam^";
const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4; const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024; const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1_000; const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 10_000;
const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3_000; const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 15_000;
function recordString( function recordString(
record: Record<string, unknown> | undefined, record: Record<string, unknown> | undefined,

View File

@@ -124,6 +124,53 @@ function resolveFlushTimerForDelay(setTimeoutSpy: ReturnType<typeof vi.spyOn>, d
return flushTimer; return flushTimer;
} }
type ScheduledTimer = {
callback: () => unknown;
handle: ReturnType<typeof setTimeout>;
};
function resolveActiveScheduledTimersForDelay(
setTimeoutSpy: ReturnType<typeof vi.spyOn>,
clearTimeoutSpy: ReturnType<typeof vi.spyOn>,
delayMs: number,
): ScheduledTimer[] {
const clearedHandles = new Set(
(clearTimeoutSpy.mock.calls as Array<Parameters<typeof clearTimeout>>).map(
([handle]) => handle,
),
);
return (setTimeoutSpy.mock.calls as Array<Parameters<typeof setTimeout>>).flatMap(
(call, index) => {
if (call[1] !== delayMs) {
return [];
}
const handle = setTimeoutSpy.mock.results[index]?.value as ReturnType<typeof setTimeout>;
if (clearedHandles.has(handle) || typeof call[0] !== "function") {
return [];
}
return [{ callback: call[0] as () => unknown, handle }];
},
);
}
async function flushActiveScheduledTimersForDelay(params: {
setTimeoutSpy: ReturnType<typeof vi.spyOn>;
clearTimeoutSpy: ReturnType<typeof vi.spyOn>;
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 }) { function createImageFetchSpy(params?: { body?: Uint8Array; contentType?: string }) {
return vi.spyOn(globalThis, "fetch").mockImplementation( return vi.spyOn(globalThis, "fetch").mockImplementation(
async () => async () =>
@@ -139,12 +186,13 @@ async function waitForBufferedProcessing() {
} }
async function waitForMockCalls(mock: { mock: { calls: unknown[] } }, count: number) { 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) { if (mock.mock.calls.length >= count) {
return; return;
} }
await delay(25); await delay(25);
} }
throw new Error(`Timed out waiting for ${count} mock call(s); got ${mock.mock.calls.length}`);
} }
function createChannelPostContext(params: { function createChannelPostContext(params: {
@@ -271,8 +319,13 @@ describe("createTelegramBot channel_post media", () => {
setOpenChannelPostConfig(); setOpenChannelPostConfig();
const fetchSpy = createImageFetchSpy(); const fetchSpy = createImageFetchSpy();
let nextTimerHandle = 1;
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout").mockImplementation(() => {
const handle = nextTimerHandle;
nextTimerHandle += 1;
return handle as unknown as ReturnType<typeof setTimeout>;
});
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
try { try {
const handler = getChannelPostHandler(); const handler = getChannelPostHandler();
await queueChannelPostAlbum(handler, { await queueChannelPostAlbum(handler, {
@@ -282,7 +335,12 @@ describe("createTelegramBot channel_post media", () => {
secondMessageId: 202, secondMessageId: 202,
}); });
expect(replySpy).not.toHaveBeenCalled(); expect(replySpy).not.toHaveBeenCalled();
await flushChannelPostMediaGroup(setTimeoutSpy); await flushActiveScheduledTimersForDelay({
setTimeoutSpy,
clearTimeoutSpy,
delayMs: TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs,
expectedCount: 1,
});
await waitForMockCalls(replySpy, 1); await waitForMockCalls(replySpy, 1);
await vi.waitFor(() => expect(replySpy).toHaveBeenCalledTimes(1)); await vi.waitFor(() => expect(replySpy).toHaveBeenCalledTimes(1));
@@ -290,6 +348,7 @@ describe("createTelegramBot channel_post media", () => {
expect(payload.Body).toContain("album caption"); expect(payload.Body).toContain("album caption");
} finally { } finally {
setTimeoutSpy.mockRestore(); setTimeoutSpy.mockRestore();
clearTimeoutSpy.mockRestore();
fetchSpy.mockRestore(); fetchSpy.mockRestore();
} }
}); });
@@ -311,7 +370,13 @@ describe("createTelegramBot channel_post media", () => {
}); });
const fetchSpy = createImageFetchSpy(); 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<typeof setTimeout>;
});
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
try { try {
const handler = getChannelPostHandlerWithRuntimeTimings(); const handler = getChannelPostHandlerWithRuntimeTimings();
await queueChannelPostAlbum(handler, { await queueChannelPostAlbum(handler, {
@@ -321,7 +386,12 @@ describe("createTelegramBot channel_post media", () => {
secondMessageId: 212, secondMessageId: 212,
}); });
expect(replySpy).not.toHaveBeenCalled(); expect(replySpy).not.toHaveBeenCalled();
await flushChannelPostMediaGroupForDelay(setTimeoutSpy, 75); await flushActiveScheduledTimersForDelay({
setTimeoutSpy,
clearTimeoutSpy,
delayMs: 75,
expectedCount: 1,
});
await waitForMockCalls(replySpy, 1); await waitForMockCalls(replySpy, 1);
await vi.waitFor(() => expect(replySpy).toHaveBeenCalledTimes(1)); await vi.waitFor(() => expect(replySpy).toHaveBeenCalledTimes(1));
@@ -329,6 +399,7 @@ describe("createTelegramBot channel_post media", () => {
expect(payload.Body).toContain("configured album"); expect(payload.Body).toContain("configured album");
} finally { } finally {
setTimeoutSpy.mockRestore(); setTimeoutSpy.mockRestore();
clearTimeoutSpy.mockRestore();
fetchSpy.mockRestore(); fetchSpy.mockRestore();
} }
}); });

View File

@@ -10,7 +10,7 @@ import { handleInvoke } from "./invoke.js";
describe("node host invoke", () => { describe("node host invoke", () => {
it.runIf(process.platform !== "win32")( 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 () => { async () => {
const request = vi.fn<GatewayClient["request"]>().mockResolvedValue(null); const request = vi.fn<GatewayClient["request"]>().mockResolvedValue(null);
const skillBins: SkillBinsProvider = { current: async () => [] }; const skillBins: SkillBinsProvider = { current: async () => [] };
@@ -21,7 +21,7 @@ describe("node host invoke", () => {
nodeId: "node-1", nodeId: "node-1",
command: "system.run.prepare", command: "system.run.prepare",
paramsJSON: JSON.stringify({ paramsJSON: JSON.stringify({
command: ["/bin/sh", "-lc", "/bin/echo ok"], command: ["/bin/echo", "ok"],
rawCommand: "/bin/echo ok", rawCommand: "/bin/echo ok",
}), }),
}, },
@@ -29,7 +29,9 @@ describe("node host invoke", () => {
skillBins, 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 { const payload = JSON.parse(result?.payloadJSON ?? "{}") as {
allowAlwaysCoverage?: { allowAlwaysCoverage?: {
complete?: boolean; complete?: boolean;

View File

@@ -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", "retries generated state cleanup after a failed removal",
async () => { async () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-e2e-temp-state-retry-")); const root = mkdtempSync(path.join(tmpdir(), "openclaw-e2e-temp-state-retry-"));

View File

@@ -78,7 +78,7 @@ describe("package-mac-app plist stamping", () => {
writeFileSync( writeFileSync(
corepackPath, corepackPath,
[ [
"#!/usr/bin/env bash", "#!/bin/bash",
"set -euo pipefail", "set -euo pipefail",
'printf \'%s|%s\\n\' "$PWD" "$*" >> "$OPENCLAW_TEST_LOG"', 'printf \'%s|%s\\n\' "$PWD" "$*" >> "$OPENCLAW_TEST_LOG"',
'if [[ "${1:-}" == "pnpm" && "${2:-}" == "--version" ]]; then', 'if [[ "${1:-}" == "pnpm" && "${2:-}" == "--version" ]]; then',
@@ -95,7 +95,7 @@ describe("package-mac-app plist stamping", () => {
ROOT_DIR=${JSON.stringify(tempRoot)} ROOT_DIR=${JSON.stringify(tempRoot)}
OPENCLAW_TEST_LOG=${JSON.stringify(logPath)} OPENCLAW_TEST_LOG=${JSON.stringify(logPath)}
export OPENCLAW_TEST_LOG export OPENCLAW_TEST_LOG
PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)} PATH=${JSON.stringify(toolsDir)}
${helperBlock} ${helperBlock}
run_pnpm install --frozen-lockfile --config.node-linker=hoisted run_pnpm install --frozen-lockfile --config.node-linker=hoisted
run_pnpm build run_pnpm build
@@ -118,7 +118,7 @@ describe("package-mac-app plist stamping", () => {
const result = runHelper(` const result = runHelper(`
set -euo pipefail set -euo pipefail
ROOT_DIR=${JSON.stringify(tempRoot)} ROOT_DIR=${JSON.stringify(tempRoot)}
PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)} PATH=${JSON.stringify(toolsDir)}
${helperBlock} ${helperBlock}
run_pnpm build run_pnpm build
`); `);