openclaw/extensions/whatsapp/src/login-qr.test.ts

93 lines
3.0 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { startWebLoginWithQr, waitForWebLogin } from "./login-qr.js";
import {
createWaSocket,
logoutWeb,
waitForCredsSaveQueueWithTimeout,
waitForWaConnection,
} from "./session.js";
vi.mock("./session.js", () => {
const createWaSocket = vi.fn(
async (_printQr: boolean, _verbose: boolean, opts?: { onQr?: (qr: string) => void }) => {
const sock = { ws: { close: vi.fn() } };
if (opts?.onQr) {
setImmediate(() => opts.onQr?.("qr-data"));
}
return sock;
},
);
const waitForWaConnection = vi.fn();
const formatError = vi.fn((err: unknown) => `formatted:${String(err)}`);
const getStatusCode = vi.fn(
(err: unknown) =>
(err as { output?: { statusCode?: number } })?.output?.statusCode ??
(err as { status?: number })?.status ??
(err as { error?: { output?: { statusCode?: number } } })?.error?.output?.statusCode,
);
const webAuthExists = vi.fn(async () => false);
const readWebSelfId = vi.fn(() => ({ e164: null, jid: null }));
const logoutWeb = vi.fn(async () => true);
const waitForCredsSaveQueueWithTimeout = vi.fn(async () => {});
return {
createWaSocket,
waitForWaConnection,
formatError,
getStatusCode,
webAuthExists,
readWebSelfId,
logoutWeb,
waitForCredsSaveQueueWithTimeout,
};
});
vi.mock("./qr-image.js", () => ({
renderQrPngBase64: vi.fn(async () => "base64"),
}));
const createWaSocketMock = vi.mocked(createWaSocket);
const waitForWaConnectionMock = vi.mocked(waitForWaConnection);
const waitForCredsSaveQueueWithTimeoutMock = vi.mocked(waitForCredsSaveQueueWithTimeout);
const logoutWebMock = vi.mocked(logoutWeb);
async function flushTasks() {
await Promise.resolve();
await Promise.resolve();
}
describe("login-qr", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("restarts login once on status 515 and completes", async () => {
let releaseCredsFlush: (() => void) | undefined;
const credsFlushGate = new Promise<void>((resolve) => {
releaseCredsFlush = resolve;
});
waitForWaConnectionMock
// Baileys v7 wraps the error: { error: BoomError(515) }
.mockRejectedValueOnce({ error: { output: { statusCode: 515 } } })
.mockResolvedValueOnce(undefined);
waitForCredsSaveQueueWithTimeoutMock.mockReturnValueOnce(credsFlushGate);
const start = await startWebLoginWithQr({ timeoutMs: 5000 });
expect(start.qrDataUrl).toBe("data:image/png;base64,base64");
const resultPromise = waitForWebLogin({ timeoutMs: 5000 });
await flushTasks();
await flushTasks();
expect(createWaSocketMock).toHaveBeenCalledTimes(1);
expect(waitForCredsSaveQueueWithTimeoutMock).toHaveBeenCalledOnce();
expect(waitForCredsSaveQueueWithTimeoutMock).toHaveBeenCalledWith(expect.any(String));
releaseCredsFlush?.();
const result = await resultPromise;
expect(result.connected).toBe(true);
expect(createWaSocketMock).toHaveBeenCalledTimes(2);
expect(logoutWebMock).not.toHaveBeenCalled();
});
});