From 758fbc2fcc29929975b8739eab5e9edb561ffc0a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 05:04:14 +0000 Subject: [PATCH] test(web): consolidate deliver reply retry coverage --- src/web/auto-reply/deliver-reply.test.ts | 52 ++++++++++++++ test/auto-reply.retry.test.ts | 89 ------------------------ 2 files changed, 52 insertions(+), 89 deletions(-) delete mode 100644 test/auto-reply.retry.test.ts diff --git a/src/web/auto-reply/deliver-reply.test.ts b/src/web/auto-reply/deliver-reply.test.ts index d08072b379..d939cc19c2 100644 --- a/src/web/auto-reply/deliver-reply.test.ts +++ b/src/web/auto-reply/deliver-reply.test.ts @@ -15,6 +15,7 @@ vi.mock("../../utils.js", async (importOriginal) => { }); const { loadWebMedia } = await import("../media.js"); +const { sleep } = await import("../../utils.js"); function makeMsg(): WebInboundMsg { return { @@ -50,6 +51,28 @@ describe("deliverWebReply", () => { expect(replyLogger.info).toHaveBeenCalledWith(expect.any(Object), "auto-reply sent (text)"); }); + it("retries text send on transient failure", async () => { + const msg = makeMsg(); + (msg.reply as unknown as { mockRejectedValueOnce: (v: unknown) => void }).mockRejectedValueOnce( + new Error("connection closed"), + ); + (msg.reply as unknown as { mockResolvedValueOnce: (v: unknown) => void }).mockResolvedValueOnce( + undefined, + ); + + await deliverWebReply({ + replyResult: { text: "hi" }, + msg, + maxMediaBytes: 1024 * 1024, + textLimit: 200, + replyLogger, + skipLog: true, + }); + + expect(msg.reply).toHaveBeenCalledTimes(2); + expect(sleep).toHaveBeenCalledWith(500); + }); + it("sends image media with caption and then remaining text", async () => { const msg = makeMsg(); ( @@ -80,6 +103,35 @@ describe("deliverWebReply", () => { expect(replyLogger.info).toHaveBeenCalledWith(expect.any(Object), "auto-reply sent (media)"); }); + it("retries media send on transient failure", async () => { + const msg = makeMsg(); + ( + loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void } + ).mockResolvedValueOnce({ + buffer: Buffer.from("img"), + contentType: "image/jpeg", + kind: "image", + }); + ( + msg.sendMedia as unknown as { mockRejectedValueOnce: (v: unknown) => void } + ).mockRejectedValueOnce(new Error("socket reset")); + ( + msg.sendMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void } + ).mockResolvedValueOnce(undefined); + + await deliverWebReply({ + replyResult: { text: "caption", mediaUrl: "http://example.com/img.jpg" }, + msg, + maxMediaBytes: 1024 * 1024, + textLimit: 200, + replyLogger, + skipLog: true, + }); + + expect(msg.sendMedia).toHaveBeenCalledTimes(2); + expect(sleep).toHaveBeenCalledWith(500); + }); + it("falls back to text-only when the first media send fails", async () => { const msg = makeMsg(); ( diff --git a/test/auto-reply.retry.test.ts b/test/auto-reply.retry.test.ts deleted file mode 100644 index b3a773b289..0000000000 --- a/test/auto-reply.retry.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; - -vi.mock("../src/web/media.js", () => ({ - loadWebMedia: vi.fn(async () => ({ - buffer: Buffer.from("img"), - contentType: "image/jpeg", - kind: "image", - fileName: "img.jpg", - })), -})); - -import type { WebInboundMessage } from "../src/web/inbound.js"; -import { defaultRuntime } from "../src/runtime.js"; -import { deliverWebReply } from "../src/web/auto-reply.js"; - -const noopLogger = { - info: vi.fn(), - warn: vi.fn(), -}; - -function makeMsg(): WebInboundMessage { - const reply = vi.fn< - Parameters, - ReturnType - >(); - const sendMedia = vi.fn< - Parameters, - ReturnType - >(); - const sendComposing = vi.fn< - Parameters, - ReturnType - >(); - return { - from: "+10000000000", - conversationId: "+10000000000", - to: "+20000000000", - id: "abc", - body: "hello", - chatType: "direct", - chatId: "chat-1", - sendComposing, - reply, - sendMedia, - }; -} - -describe("deliverWebReply retry", () => { - it("retries text send on transient failure", async () => { - const msg = makeMsg(); - msg.reply.mockRejectedValueOnce(new Error("connection closed")); - msg.reply.mockResolvedValueOnce(undefined); - - await expect( - deliverWebReply({ - replyResult: { text: "hi" }, - msg, - maxMediaBytes: 5_000_000, - replyLogger: noopLogger, - runtime: defaultRuntime, - skipLog: true, - }), - ).resolves.toBeUndefined(); - - expect(msg.reply).toHaveBeenCalledTimes(2); - }); - - it("retries media send on transient failure", async () => { - const msg = makeMsg(); - msg.sendMedia.mockRejectedValueOnce(new Error("socket reset")); - msg.sendMedia.mockResolvedValueOnce(undefined); - - await expect( - deliverWebReply({ - replyResult: { - text: "caption", - mediaUrl: "http://example.com/img.jpg", - }, - msg, - maxMediaBytes: 5_000_000, - replyLogger: noopLogger, - runtime: defaultRuntime, - skipLog: true, - }), - ).resolves.toBeUndefined(); - - expect(msg.sendMedia).toHaveBeenCalledTimes(2); - }); -});