diff --git a/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.runtime.test.ts b/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.runtime.test.ts index 0b02f9c79ae0..86db047d1f2d 100644 --- a/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.runtime.test.ts +++ b/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.runtime.test.ts @@ -26,6 +26,16 @@ import { import { getWhatsAppQaScenarioDefinition } from "./whatsapp-live.scenarios.js"; import { unpackWhatsAppAuthArchive } from "./whatsapp-live.setup.js"; +const runExecSpy = vi.hoisted(() => + vi.fn(), +); + +vi.mock("openclaw/plugin-sdk/process-runtime", async (importOriginal) => { + const actual = await importOriginal(); + runExecSpy.mockImplementation(actual.runExec); + return { ...actual, runExec: runExecSpy }; +}); + const testing = { buildWhatsAppQaConfig, callWhatsAppGatewayMessageAction, @@ -233,6 +243,7 @@ describe("WhatsApp QA live runtime", () => { it("unpacks auth archives into a caller-provided temp directory", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-wa-qa-test-")); try { + runExecSpy.mockClear(); const archiveBase64 = await createTgz({ root: tempRoot, entries: { @@ -249,6 +260,15 @@ describe("WhatsApp QA live runtime", () => { await expect(fs.readFile(path.join(authDir, "session/key.json"), "utf8")).resolves.toBe( "{}\n", ); + const archivePath = path.join(tempRoot, "driver.tgz"); + const execOptions = { logOutput: false, timeoutMs: 60_000 }; + expect(runExecSpy).toHaveBeenNthCalledWith(1, "tar", ["-tzf", archivePath], execOptions); + expect(runExecSpy).toHaveBeenNthCalledWith( + 2, + "tar", + ["-xzf", archivePath, "-C", authDir], + execOptions, + ); } finally { await fs.rm(tempRoot, { recursive: true, force: true }); } diff --git a/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.setup.ts b/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.setup.ts index 0bf4bad55ed3..c454679d0c82 100644 --- a/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.setup.ts +++ b/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.setup.ts @@ -7,6 +7,7 @@ import type { WhatsAppQaGateway } from "./whatsapp-live.contracts.js"; const WHATSAPP_QA_READY_TIMEOUT_MS = 150_000; const WHATSAPP_QA_READY_STABILITY_MS = 20_000; +const WHATSAPP_QA_AUTH_ARCHIVE_TIMEOUT_MS = 60_000; const WHATSAPP_QA_SIGNAL_SESSION_FILE_RE = /^session-[^/\\]+\.json$/u; type WhatsAppChannelStatus = { @@ -114,7 +115,10 @@ export async function waitForWhatsAppChannelStable(gateway: WhatsAppQaGateway, a } async function listTarEntries(archivePath: string): Promise { - const { stdout } = await runExec("tar", ["-tzf", archivePath], { logOutput: false }); + const { stdout } = await runExec("tar", ["-tzf", archivePath], { + logOutput: false, + timeoutMs: WHATSAPP_QA_AUTH_ARCHIVE_TIMEOUT_MS, + }); return normalizeStringEntries(stdout.split("\n")); } @@ -141,7 +145,10 @@ export async function unpackWhatsAppAuthArchive(params: { await fs.writeFile(archivePath, Buffer.from(params.archiveBase64, "base64"), { mode: 0o600 }); const entries = await listTarEntries(archivePath); assertSafeArchiveEntries(entries); - await runExec("tar", ["-xzf", archivePath, "-C", authDir], { logOutput: false }); + await runExec("tar", ["-xzf", archivePath, "-C", authDir], { + logOutput: false, + timeoutMs: WHATSAPP_QA_AUTH_ARCHIVE_TIMEOUT_MS, + }); await fs.rm(archivePath, { force: true }); if (params.clearSignalSessions === true) { await clearWhatsAppAuthSignalSessions(authDir);