test(routing): add route-reply coverage
This commit is contained in:
@@ -25,6 +25,8 @@ export async function dispatchReplyFromConfig(params: {
|
|||||||
// This handles cross-provider routing (e.g., message from Telegram being processed
|
// This handles cross-provider routing (e.g., message from Telegram being processed
|
||||||
// by a shared session that's currently on Slack) while preserving normal dispatcher
|
// by a shared session that's currently on Slack) while preserving normal dispatcher
|
||||||
// flow when the provider handles its own messages.
|
// flow when the provider handles its own messages.
|
||||||
|
//
|
||||||
|
// Debug: `pnpm test src/auto-reply/reply/dispatch-from-config.test.ts`
|
||||||
const originatingChannel = ctx.OriginatingChannel;
|
const originatingChannel = ctx.OriginatingChannel;
|
||||||
const originatingTo = ctx.OriginatingTo;
|
const originatingTo = ctx.OriginatingTo;
|
||||||
const currentSurface = (ctx.Surface ?? ctx.Provider)?.toLowerCase();
|
const currentSurface = (ctx.Surface ?? ctx.Provider)?.toLowerCase();
|
||||||
|
|||||||
@@ -442,6 +442,10 @@ export function scheduleFollowupDrain(
|
|||||||
while (queue.items.length > 0 || queue.droppedCount > 0) {
|
while (queue.items.length > 0 || queue.droppedCount > 0) {
|
||||||
await waitForQueueDebounce(queue);
|
await waitForQueueDebounce(queue);
|
||||||
if (queue.mode === "collect") {
|
if (queue.mode === "collect") {
|
||||||
|
// Once the batch is mixed, never collect again within this drain.
|
||||||
|
// Prevents “collect after shift” collapsing different targets.
|
||||||
|
//
|
||||||
|
// Debug: `pnpm test src/auto-reply/reply/queue.collect-routing.test.ts`
|
||||||
if (forceIndividualCollect) {
|
if (forceIndividualCollect) {
|
||||||
const next = queue.items.shift();
|
const next = queue.items.shift();
|
||||||
if (!next) break;
|
if (!next) break;
|
||||||
|
|||||||
115
src/auto-reply/reply/route-reply.test.ts
Normal file
115
src/auto-reply/reply/route-reply.test.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => ({
|
||||||
|
sendMessageDiscord: vi.fn(async () => ({ messageId: "m1", channelId: "c1" })),
|
||||||
|
sendMessageIMessage: vi.fn(async () => ({ messageId: "ok" })),
|
||||||
|
sendMessageSignal: vi.fn(async () => ({ messageId: "t1" })),
|
||||||
|
sendMessageSlack: vi.fn(async () => ({ messageId: "m1", channelId: "c1" })),
|
||||||
|
sendMessageTelegram: vi.fn(async () => ({ messageId: "m1", chatId: "c1" })),
|
||||||
|
sendMessageWhatsApp: vi.fn(async () => ({ messageId: "m1", toJid: "jid" })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../discord/send.js", () => ({
|
||||||
|
sendMessageDiscord: mocks.sendMessageDiscord,
|
||||||
|
}));
|
||||||
|
vi.mock("../../imessage/send.js", () => ({
|
||||||
|
sendMessageIMessage: mocks.sendMessageIMessage,
|
||||||
|
}));
|
||||||
|
vi.mock("../../signal/send.js", () => ({
|
||||||
|
sendMessageSignal: mocks.sendMessageSignal,
|
||||||
|
}));
|
||||||
|
vi.mock("../../slack/send.js", () => ({
|
||||||
|
sendMessageSlack: mocks.sendMessageSlack,
|
||||||
|
}));
|
||||||
|
vi.mock("../../telegram/send.js", () => ({
|
||||||
|
sendMessageTelegram: mocks.sendMessageTelegram,
|
||||||
|
}));
|
||||||
|
vi.mock("../../web/outbound.js", () => ({
|
||||||
|
sendMessageWhatsApp: mocks.sendMessageWhatsApp,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { routeReply } = await import("./route-reply.js");
|
||||||
|
|
||||||
|
describe("routeReply", () => {
|
||||||
|
it("no-ops on empty payload", async () => {
|
||||||
|
mocks.sendMessageSlack.mockClear();
|
||||||
|
const res = await routeReply({
|
||||||
|
payload: {},
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C123",
|
||||||
|
cfg: {} as never,
|
||||||
|
});
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
expect(mocks.sendMessageSlack).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes thread id to Telegram sends", async () => {
|
||||||
|
mocks.sendMessageTelegram.mockClear();
|
||||||
|
await routeReply({
|
||||||
|
payload: { text: "hi" },
|
||||||
|
channel: "telegram",
|
||||||
|
to: "telegram:123",
|
||||||
|
threadId: 42,
|
||||||
|
cfg: {} as never,
|
||||||
|
});
|
||||||
|
expect(mocks.sendMessageTelegram).toHaveBeenCalledWith(
|
||||||
|
"telegram:123",
|
||||||
|
"hi",
|
||||||
|
expect.objectContaining({ messageThreadId: 42 }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses replyToId as threadTs for Slack", async () => {
|
||||||
|
mocks.sendMessageSlack.mockClear();
|
||||||
|
await routeReply({
|
||||||
|
payload: { text: "hi", replyToId: "1710000000.0001" },
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C123",
|
||||||
|
cfg: {} as never,
|
||||||
|
});
|
||||||
|
expect(mocks.sendMessageSlack).toHaveBeenCalledWith(
|
||||||
|
"channel:C123",
|
||||||
|
"hi",
|
||||||
|
expect.objectContaining({ threadTs: "1710000000.0001" }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends multiple mediaUrls (caption only on first)", async () => {
|
||||||
|
mocks.sendMessageSlack.mockClear();
|
||||||
|
await routeReply({
|
||||||
|
payload: { text: "caption", mediaUrls: ["a", "b"] },
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C123",
|
||||||
|
cfg: {} as never,
|
||||||
|
});
|
||||||
|
expect(mocks.sendMessageSlack).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mocks.sendMessageSlack).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
"channel:C123",
|
||||||
|
"caption",
|
||||||
|
expect.objectContaining({ mediaUrl: "a" }),
|
||||||
|
);
|
||||||
|
expect(mocks.sendMessageSlack).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
"channel:C123",
|
||||||
|
"",
|
||||||
|
expect.objectContaining({ mediaUrl: "b" }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("routes WhatsApp via outbound sender (accountId honored)", async () => {
|
||||||
|
mocks.sendMessageWhatsApp.mockClear();
|
||||||
|
await routeReply({
|
||||||
|
payload: { text: "hi" },
|
||||||
|
channel: "whatsapp",
|
||||||
|
to: "+15551234567",
|
||||||
|
accountId: "acc-1",
|
||||||
|
cfg: {} as never,
|
||||||
|
});
|
||||||
|
expect(mocks.sendMessageWhatsApp).toHaveBeenCalledWith(
|
||||||
|
"+15551234567",
|
||||||
|
"hi",
|
||||||
|
expect.objectContaining({ accountId: "acc-1", verbose: false }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -54,6 +54,7 @@ export async function routeReply(
|
|||||||
): Promise<RouteReplyResult> {
|
): Promise<RouteReplyResult> {
|
||||||
const { payload, channel, to, accountId, threadId } = params;
|
const { payload, channel, to, accountId, threadId } = params;
|
||||||
|
|
||||||
|
// Debug: `pnpm test src/auto-reply/reply/route-reply.test.ts`
|
||||||
const text = payload.text ?? "";
|
const text = payload.text ?? "";
|
||||||
const mediaUrls = (payload.mediaUrls?.filter(Boolean) ?? []).length
|
const mediaUrls = (payload.mediaUrls?.filter(Boolean) ?? []).length
|
||||||
? (payload.mediaUrls?.filter(Boolean) as string[])
|
? (payload.mediaUrls?.filter(Boolean) as string[])
|
||||||
|
|||||||
Reference in New Issue
Block a user