From ec630839a96b704693de7d1da5df2546e3caedfd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 25 Nov 2025 04:30:40 +0100 Subject: [PATCH] chore: finish logger sweep and add retry tests --- src/auto-reply/reply.ts | 2 +- src/infra/retry.test.ts | 28 ++++++++++++++++++++++++++++ src/provider-web.test.ts | 7 ++++--- src/provider-web.ts | 17 +++++------------ 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 src/infra/retry.test.ts diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index 9786a1613..88893e8f7 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -309,7 +309,7 @@ export async function getReplyFromConfig( `Command auto-reply timed out after ${elapsed}ms (limit ${timeoutMs}ms)`, ); } else { - console.error(`Command auto-reply failed after ${elapsed}ms`, err); + logError("Command auto-reply failed after ms: " . String(err), runtime); } return undefined; } diff --git a/src/infra/retry.test.ts b/src/infra/retry.test.ts new file mode 100644 index 000000000..cd25bdb1f --- /dev/null +++ b/src/infra/retry.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it, vi } from "vitest"; + +import { retryAsync } from "./retry.js"; + +describe("retryAsync", () => { + it("returns on first success", async () => { + const fn = vi.fn().mockResolvedValue("ok"); + const result = await retryAsync(fn, 3, 10); + expect(result).toBe("ok"); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it("retries then succeeds", async () => { + const fn = vi + .fn() + .mockRejectedValueOnce(new Error("fail1")) + .mockResolvedValueOnce("ok"); + const result = await retryAsync(fn, 3, 1); + expect(result).toBe("ok"); + expect(fn).toHaveBeenCalledTimes(2); + }); + + it("propagates after exhausting retries", async () => { + const fn = vi.fn().mockRejectedValue(new Error("boom")); + await expect(retryAsync(fn, 2, 1)).rejects.toThrow("boom"); + expect(fn).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/provider-web.test.ts b/src/provider-web.test.ts index 4bd90f922..56f105641 100644 --- a/src/provider-web.test.ts +++ b/src/provider-web.test.ts @@ -75,9 +75,10 @@ describe("provider-web", () => { expect.objectContaining({ printQRInTerminal: false }), ); const passed = makeWASocket.mock.calls[0][0]; - expect((passed as { logger?: { level?: string } }).logger?.level).toBe( - "silent", - ); + const passedLogger = (passed as { logger?: { level?: string; trace?: unknown } }) + .logger; + expect(passedLogger?.level).toBe("silent"); + expect(typeof passedLogger?.trace).toBe("function"); const sock = getLastSocket(); const saveCreds = ( await baileys.useMultiFileAuthState.mock.results[0].value diff --git a/src/provider-web.ts b/src/provider-web.ts index 88d6c34bc..cf72534a2 100644 --- a/src/provider-web.ts +++ b/src/provider-web.ts @@ -23,18 +23,11 @@ import { logInfo, logWarn } from "./logger.js"; const WA_WEB_AUTH_DIR = path.join(os.homedir(), ".warelay", "credentials"); export async function createWaSocket(printQr: boolean, verbose: boolean) { - const logger = verbose - ? pino({ level: "info" }) - : ({ - level: "silent", - child: () => ({}) as pino.Logger, - trace: () => {}, - debug: () => {}, - info: () => {}, - warn: () => {}, - error: () => {}, - fatal: () => {}, - } satisfies Partial) as pino.Logger; + const logger = pino({ level: verbose ? "info" : "silent" }); + // Some Baileys internals call logger.trace even when silent; ensure it's present. + if (typeof (logger as Record).trace !== "function") { + (logger as unknown as { trace: () => void }).trace = () => {}; + } await ensureDir(WA_WEB_AUTH_DIR); const { state, saveCreds } = await useMultiFileAuthState(WA_WEB_AUTH_DIR); const { version } = await fetchLatestBaileysVersion();