import "./monitor-inbox.test-harness.js"; import { describe, expect, it, vi } from "vitest"; import { monitorWebInbox } from "./inbound.js"; import { DEFAULT_ACCOUNT_ID, getAuthDir, getSock, installWebMonitorInboxUnitTestHooks, } from "./monitor-inbox.test-harness.js"; describe("append upsert handling (#20952)", () => { installWebMonitorInboxUnitTestHooks(); type InboxOnMessage = NonNullable[0]["onMessage"]>; async function tick() { await new Promise((resolve) => setImmediate(resolve)); } async function startInboxMonitor(onMessage: InboxOnMessage) { const listener = await monitorWebInbox({ verbose: false, onMessage, accountId: DEFAULT_ACCOUNT_ID, authDir: getAuthDir(), }); return { listener, sock: getSock() }; } it("processes recent append messages (within 60s of connect)", async () => { const onMessage = vi.fn(async () => {}); const { listener, sock } = await startInboxMonitor(onMessage); // Timestamp ~5 seconds ago — recent, should be processed. const recentTs = Math.floor(Date.now() / 1000) - 5; sock.ev.emit("messages.upsert", { type: "append", messages: [ { key: { id: "recent-1", fromMe: false, remoteJid: "120363@g.us" }, message: { conversation: "hello from group" }, messageTimestamp: recentTs, pushName: "Tester", }, ], }); await tick(); expect(onMessage).toHaveBeenCalledTimes(1); await listener.close(); }); it("skips stale append messages (older than 60s before connect)", async () => { const onMessage = vi.fn(async () => {}); const { listener, sock } = await startInboxMonitor(onMessage); // Timestamp 5 minutes ago — stale history sync, should be skipped. const staleTs = Math.floor(Date.now() / 1000) - 300; sock.ev.emit("messages.upsert", { type: "append", messages: [ { key: { id: "stale-1", fromMe: false, remoteJid: "120363@g.us" }, message: { conversation: "old history sync" }, messageTimestamp: staleTs, pushName: "OldTester", }, ], }); await tick(); expect(onMessage).not.toHaveBeenCalled(); await listener.close(); }); it("skips append messages with NaN/non-finite timestamps", async () => { const onMessage = vi.fn(async () => {}); const { listener, sock } = await startInboxMonitor(onMessage); // NaN timestamp should be treated as 0 (stale) and skipped. sock.ev.emit("messages.upsert", { type: "append", messages: [ { key: { id: "nan-1", fromMe: false, remoteJid: "120363@g.us" }, message: { conversation: "bad timestamp" }, messageTimestamp: NaN, pushName: "BadTs", }, ], }); await tick(); expect(onMessage).not.toHaveBeenCalled(); await listener.close(); }); it("handles Long-like protobuf timestamps correctly", async () => { const onMessage = vi.fn(async () => {}); const { listener, sock } = await startInboxMonitor(onMessage); // Baileys can deliver messageTimestamp as a Long object (from protobufjs). // Number(longObj) calls valueOf() and returns the numeric value. const recentTs = Math.floor(Date.now() / 1000) - 5; const longLike = { low: recentTs, high: 0, unsigned: true, valueOf: () => recentTs }; sock.ev.emit("messages.upsert", { type: "append", messages: [ { key: { id: "long-1", fromMe: false, remoteJid: "120363@g.us" }, message: { conversation: "long timestamp" }, messageTimestamp: longLike, pushName: "LongTs", }, ], }); await tick(); expect(onMessage).toHaveBeenCalledTimes(1); await listener.close(); }); it("always processes notify messages regardless of timestamp", async () => { const onMessage = vi.fn(async () => {}); const { listener, sock } = await startInboxMonitor(onMessage); // Very old timestamp but type=notify — should always be processed. const oldTs = Math.floor(Date.now() / 1000) - 86400; sock.ev.emit("messages.upsert", { type: "notify", messages: [ { key: { id: "notify-1", fromMe: false, remoteJid: "999@s.whatsapp.net" }, message: { conversation: "normal message" }, messageTimestamp: oldTs, pushName: "User", }, ], }); await tick(); expect(onMessage).toHaveBeenCalledTimes(1); await listener.close(); }); });