test(web): consolidate deliver reply retry coverage

This commit is contained in:
Peter Steinberger
2026-02-15 05:04:14 +00:00
parent 8a50936d32
commit 758fbc2fcc
2 changed files with 52 additions and 89 deletions

View File

@@ -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();
(

View File

@@ -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<WebInboundMessage["reply"]>,
ReturnType<WebInboundMessage["reply"]>
>();
const sendMedia = vi.fn<
Parameters<WebInboundMessage["sendMedia"]>,
ReturnType<WebInboundMessage["sendMedia"]>
>();
const sendComposing = vi.fn<
Parameters<WebInboundMessage["sendComposing"]>,
ReturnType<WebInboundMessage["sendComposing"]>
>();
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);
});
});