















11// Whatsapp tests cover connection controller plugin behavior.
22import { EventEmitter } from "node:events";
3+import fs from "node:fs/promises";
4+import os from "node:os";
5+import path from "node:path";
36import { DisconnectReason } from "baileys";
47import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
58import { getRegisteredWhatsAppConnectionController } from "./connection-controller-registry.js";
@@ -8,8 +11,9 @@ import {
811waitForWhatsAppLoginResult,
912WhatsAppConnectionController,
1013} from "./connection-controller.js";
14+import { enqueueCredsSave, writeCredsJsonAtomically } from "./creds-persistence.js";
1115import { createAcceptedWhatsAppSendResult } from "./inbound/send-result.test-helper.js";
12-import { createWaSocket, waitForWaConnection } from "./session.js";
16+import { createWaSocket, readWebAuthExistsForDecision, waitForWaConnection } from "./session.js";
1317import { DEFAULT_WHATSAPP_SOCKET_TIMING } from "./socket-timing.js";
14181519vi.mock("./session.js", async () => {
@@ -18,11 +22,13 @@ vi.mock("./session.js", async () => {
1822 ...actual,
1923createWaSocket: vi.fn(),
2024waitForWaConnection: vi.fn(),
25+readWebAuthExistsForDecision: vi.fn(async () => ({ outcome: "stable" as const, exists: true })),
2126};
2227});
23282429const createWaSocketMock = vi.mocked(createWaSocket);
2530const waitForWaConnectionMock = vi.mocked(waitForWaConnection);
31+const readWebAuthExistsForDecisionMock = vi.mocked(readWebAuthExistsForDecision);
26322733function createListenerStub(messageId = "ok") {
2834return {
@@ -47,6 +53,9 @@ describe("WhatsAppConnectionController", () => {
47534854beforeEach(() => {
4955vi.clearAllMocks();
56+readWebAuthExistsForDecisionMock
57+.mockReset()
58+.mockResolvedValue({ outcome: "stable", exists: true });
5059controller = new WhatsAppConnectionController({
5160accountId: "work",
5261authDir: "/tmp/wa-auth",
@@ -249,6 +258,102 @@ describe("WhatsAppConnectionController", () => {
249258expect(waitForConnection).toHaveBeenCalledTimes(2);
250259});
251260261+it("returns a retryable failure when the socket opens before auth persistence settles", async () => {
262+readWebAuthExistsForDecisionMock.mockResolvedValue({ outcome: "unstable" });
263+const waitForConnection = vi.fn().mockResolvedValueOnce(undefined);
264+265+const result = await waitForWhatsAppLoginResult({
266+sock: createSocketWithTransportEmitter() as never,
267+authDir: "/tmp/wa-auth",
268+isLegacyAuthDir: false,
269+verbose: false,
270+runtime: { log: vi.fn() } as never,
271+waitForConnection: waitForConnection as never,
272+});
273+274+expect(result.outcome).toBe("failed");
275+if (result.outcome === "failed") {
276+expect(result.message).toMatch(/retry/i);
277+expect((result.error as { code?: string })?.code).toBe("whatsapp-auth-unstable");
278+}
279+});
280+281+it("returns a retryable failure when auth is not linked on disk after the socket opens", async () => {
282+readWebAuthExistsForDecisionMock.mockResolvedValue({ outcome: "stable", exists: false });
283+const waitForConnection = vi.fn().mockResolvedValueOnce(undefined);
284+285+const result = await waitForWhatsAppLoginResult({
286+sock: createSocketWithTransportEmitter() as never,
287+authDir: "/tmp/wa-auth",
288+isLegacyAuthDir: false,
289+verbose: false,
290+runtime: { log: vi.fn() } as never,
291+waitForConnection: waitForConnection as never,
292+});
293+294+expect(result.outcome).toBe("failed");
295+if (result.outcome === "failed") {
296+expect(result.message).toMatch(/retry/i);
297+expect((result.error as { code?: string })?.code).toBe("whatsapp-auth-unstable");
298+}
299+});
300+301+it("returns connected only after auth is confirmed durable on disk", async () => {
302+readWebAuthExistsForDecisionMock.mockResolvedValue({ outcome: "stable", exists: true });
303+const waitForConnection = vi.fn().mockResolvedValueOnce(undefined);
304+const sock = createSocketWithTransportEmitter();
305+306+const result = await waitForWhatsAppLoginResult({
307+sock: sock as never,
308+authDir: "/tmp/wa-auth",
309+isLegacyAuthDir: false,
310+verbose: false,
311+runtime: { log: vi.fn() } as never,
312+waitForConnection: waitForConnection as never,
313+});
314+315+expect(result).toEqual({ outcome: "connected", restarted: false, sock });
316+expect(readWebAuthExistsForDecisionMock).toHaveBeenCalledWith("/tmp/wa-auth");
317+});
318+319+it("waits for queued creds persistence so linked auth survives an auth-dir reuse", async () => {
320+const actualSession = await vi.importActual<typeof import("./session.js")>("./session.js");
321+const authDir = await fs.mkdtemp(path.join(os.tmpdir(), "wa-auth-durability-"));
322+try {
323+readWebAuthExistsForDecisionMock.mockImplementation(
324+actualSession.readWebAuthExistsForDecision,
325+);
326+let credsSaved = false;
327+enqueueCredsSave(
328+authDir,
329+async () => {
330+await new Promise((resolve) => {
331+setTimeout(resolve, 50);
332+});
333+await writeCredsJsonAtomically(authDir, { me: { id: "123@s.whatsapp.net" } });
334+credsSaved = true;
335+},
336+() => {},
337+);
338+339+const result = await waitForWhatsAppLoginResult({
340+sock: createSocketWithTransportEmitter() as never,
341+ authDir,
342+isLegacyAuthDir: false,
343+verbose: false,
344+runtime: { log: vi.fn() } as never,
345+waitForConnection: vi.fn().mockResolvedValueOnce(undefined) as never,
346+});
347+348+expect(credsSaved).toBe(true);
349+expect(result.outcome).toBe("connected");
350+// A fresh read of the same auth dir is what a restarted/rebuilt container does.
351+await expect(actualSession.webAuthExists(authDir)).resolves.toBe(true);
352+} finally {
353+await fs.rm(authDir, { recursive: true, force: true });
354+}
355+});
356+252357it("keeps the previous registered controller until a replacement listener is ready", async () => {
253358const liveController = new WhatsAppConnectionController({
254359accountId: "work",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。