diff --git a/src/process/supervisor/adapters/child.test.ts b/src/process/supervisor/adapters/child.test.ts index f69a67f141b..691c98d0767 100644 --- a/src/process/supervisor/adapters/child.test.ts +++ b/src/process/supervisor/adapters/child.test.ts @@ -28,6 +28,25 @@ function createStubChild(pid = 1234) { return { child, killMock }; } +async function createAdapterHarness(params?: { + pid?: number; + argv?: string[]; + env?: NodeJS.ProcessEnv; +}) { + const { createChildAdapter } = await import("./child.js"); + const { child, killMock } = createStubChild(params?.pid); + spawnWithFallbackMock.mockResolvedValue({ + child, + usedFallback: false, + }); + const adapter = await createChildAdapter({ + argv: params?.argv ?? ["node", "-e", "setTimeout(() => {}, 1000)"], + env: params?.env, + stdinMode: "pipe-open", + }); + return { adapter, killMock }; +} + describe("createChildAdapter", () => { beforeEach(() => { spawnWithFallbackMock.mockReset(); @@ -35,16 +54,7 @@ describe("createChildAdapter", () => { }); it("uses process-tree kill for default SIGKILL", async () => { - const { child, killMock } = createStubChild(4321); - spawnWithFallbackMock.mockResolvedValue({ - child, - usedFallback: false, - }); - const { createChildAdapter } = await import("./child.js"); - const adapter = await createChildAdapter({ - argv: ["node", "-e", "setTimeout(() => {}, 1000)"], - stdinMode: "pipe-open", - }); + const { adapter, killMock } = await createAdapterHarness({ pid: 4321 }); const spawnArgs = spawnWithFallbackMock.mock.calls[0]?.[0] as { options?: { detached?: boolean }; @@ -60,16 +70,7 @@ describe("createChildAdapter", () => { }); it("uses direct child.kill for non-SIGKILL signals", async () => { - const { child, killMock } = createStubChild(7654); - spawnWithFallbackMock.mockResolvedValue({ - child, - usedFallback: false, - }); - const { createChildAdapter } = await import("./child.js"); - const adapter = await createChildAdapter({ - argv: ["node", "-e", "setTimeout(() => {}, 1000)"], - stdinMode: "pipe-open", - }); + const { adapter, killMock } = await createAdapterHarness({ pid: 7654 }); adapter.kill("SIGTERM"); @@ -78,15 +79,9 @@ describe("createChildAdapter", () => { }); it("keeps inherited env when no override env is provided", async () => { - const { child } = createStubChild(3333); - spawnWithFallbackMock.mockResolvedValue({ - child, - usedFallback: false, - }); - const { createChildAdapter } = await import("./child.js"); - await createChildAdapter({ + await createAdapterHarness({ + pid: 3333, argv: ["node", "-e", "process.exit(0)"], - stdinMode: "pipe-open", }); const spawnArgs = spawnWithFallbackMock.mock.calls[0]?.[0] as { @@ -96,16 +91,10 @@ describe("createChildAdapter", () => { }); it("passes explicit env overrides as strings", async () => { - const { child } = createStubChild(4444); - spawnWithFallbackMock.mockResolvedValue({ - child, - usedFallback: false, - }); - const { createChildAdapter } = await import("./child.js"); - await createChildAdapter({ + await createAdapterHarness({ + pid: 4444, argv: ["node", "-e", "process.exit(0)"], env: { FOO: "bar", COUNT: "12", DROP_ME: undefined }, - stdinMode: "pipe-open", }); const spawnArgs = spawnWithFallbackMock.mock.calls[0]?.[0] as { diff --git a/src/slack/monitor/message-handler/prepare.test.ts b/src/slack/monitor/message-handler/prepare.test.ts index 778a5949996..747d666ea0e 100644 --- a/src/slack/monitor/message-handler/prepare.test.ts +++ b/src/slack/monitor/message-handler/prepare.test.ts @@ -112,6 +112,40 @@ describe("slack prepareSlackMessage inbound contract", () => { }); } + function createSlackAccount(config: ResolvedSlackAccount["config"] = {}): ResolvedSlackAccount { + return { + accountId: "default", + enabled: true, + botTokenSource: "config", + appTokenSource: "config", + config, + }; + } + + function createSlackMessage(overrides: Partial): SlackMessageEvent { + return { + channel: "D123", + channel_type: "im", + user: "U1", + text: "hi", + ts: "1.000", + ...overrides, + } as SlackMessageEvent; + } + + async function prepareMessageWith( + ctx: SlackMonitorContext, + account: ResolvedSlackAccount, + message: SlackMessageEvent, + ) { + return prepareSlackMessage({ + ctx, + account, + message, + opts: { source: "message" }, + }); + } + function createThreadSlackCtx(params: { cfg: OpenClawConfig; replies: unknown }) { return createInboundSlackCtx({ cfg: params.cfg, @@ -174,28 +208,14 @@ describe("slack prepareSlackMessage inbound contract", () => { }; slackCtx.resolveChannelName = async () => channelInfo; - const account: ResolvedSlackAccount = { - accountId: "default", - enabled: true, - botTokenSource: "config", - appTokenSource: "config", - config: {}, - }; - - const message: SlackMessageEvent = { - channel: "C123", - channel_type: "channel", - user: "U1", - text: "hi", - ts: "1.000", - } as SlackMessageEvent; - - const prepared = await prepareSlackMessage({ - ctx: slackCtx, - account, - message, - opts: { source: "message" }, - }); + const prepared = await prepareMessageWith( + slackCtx, + createSlackAccount(), + createSlackMessage({ + channel: "C123", + channel_type: "channel", + }), + ); expect(prepared).toBeTruthy(); expect(prepared!.ctxPayload.GroupSystemPrompt).toBe("Config prompt"); @@ -216,28 +236,11 @@ describe("slack prepareSlackMessage inbound contract", () => { // oxlint-disable-next-line typescript/no-explicit-any slackCtx.resolveUserName = async () => ({ name: "Alice" }) as any; - const account: ResolvedSlackAccount = { - accountId: "default", - enabled: true, - botTokenSource: "config", - appTokenSource: "config", - config: { replyToMode: "all" }, - }; - - const message: SlackMessageEvent = { - channel: "D123", - channel_type: "im", - user: "U1", - text: "hi", - ts: "1.000", - } as SlackMessageEvent; - - const prepared = await prepareSlackMessage({ - ctx: slackCtx, - account, - message, - opts: { source: "message" }, - }); + const prepared = await prepareMessageWith( + slackCtx, + createSlackAccount({ replyToMode: "all" }), + createSlackMessage({}), + ); expect(prepared).toBeTruthy(); expect(prepared!.ctxPayload.MessageThreadId).toBe("1.000"); @@ -271,23 +274,17 @@ describe("slack prepareSlackMessage inbound contract", () => { }); slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" }); - const account = createThreadAccount(); - - const message: SlackMessageEvent = { - channel: "C123", - channel_type: "channel", - user: "U1", - text: "current message", - ts: "101.000", - thread_ts: "100.000", - } as SlackMessageEvent; - - const prepared = await prepareSlackMessage({ - ctx: slackCtx, - account, - message, - opts: { source: "message" }, - }); + const prepared = await prepareMessageWith( + slackCtx, + createThreadAccount(), + createSlackMessage({ + channel: "C123", + channel_type: "channel", + text: "current message", + ts: "101.000", + thread_ts: "100.000", + }), + ); expect(prepared).toBeTruthy(); expect(prepared!.ctxPayload.IsFirstThreadTurn).toBe(true); @@ -326,23 +323,17 @@ describe("slack prepareSlackMessage inbound contract", () => { slackCtx.resolveUserName = async () => ({ name: "Alice" }); slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" }); - const account = createThreadAccount(); - - const message: SlackMessageEvent = { - channel: "C123", - channel_type: "channel", - user: "U1", - text: "reply in old thread", - ts: "201.000", - thread_ts: "200.000", - } as SlackMessageEvent; - - const prepared = await prepareSlackMessage({ - ctx: slackCtx, - account, - message, - opts: { source: "message" }, - }); + const prepared = await prepareMessageWith( + slackCtx, + createThreadAccount(), + createSlackMessage({ + channel: "C123", + channel_type: "channel", + text: "reply in old thread", + ts: "201.000", + thread_ts: "200.000", + }), + ); expect(prepared).toBeTruthy(); expect(prepared!.ctxPayload.IsFirstThreadTurn).toBeUndefined(); @@ -350,15 +341,12 @@ describe("slack prepareSlackMessage inbound contract", () => { }); it("includes thread_ts and parent_user_id metadata in thread replies", async () => { - const message: SlackMessageEvent = { - channel: "D123", - channel_type: "im", - user: "U1", + const message = createSlackMessage({ text: "this is a reply", ts: "1.002", thread_ts: "1.000", parent_user_id: "U2", - } as SlackMessageEvent; + }); const prepared = await prepareWithDefaultCtx(message); @@ -370,13 +358,7 @@ describe("slack prepareSlackMessage inbound contract", () => { }); it("excludes thread_ts from top-level messages", async () => { - const message: SlackMessageEvent = { - channel: "D123", - channel_type: "im", - user: "U1", - text: "hello", - ts: "1.000", - } as SlackMessageEvent; + const message = createSlackMessage({ text: "hello" }); const prepared = await prepareWithDefaultCtx(message); @@ -387,14 +369,10 @@ describe("slack prepareSlackMessage inbound contract", () => { }); it("excludes thread metadata when thread_ts equals ts without parent_user_id", async () => { - const message: SlackMessageEvent = { - channel: "D123", - channel_type: "im", - user: "U1", + const message = createSlackMessage({ text: "top level", - ts: "1.000", thread_ts: "1.000", - } as SlackMessageEvent; + }); const prepared = await prepareWithDefaultCtx(message); @@ -463,26 +441,30 @@ describe("prepareSlackMessage sender prefix", () => { } as unknown as SlackMonitorContext; } - it("prefixes channel bodies with sender label", async () => { - const ctx = createSenderPrefixCtx({ - channels: {}, - slashCommand: { command: "/openclaw", enabled: true }, - }); - - const result = await prepareSlackMessage({ + async function prepareSenderPrefixMessage(ctx: SlackMonitorContext, text: string, ts: string) { + return prepareSlackMessage({ ctx, account: { accountId: "default", config: {} } as never, message: { type: "message", channel: "C1", channel_type: "channel", - text: "<@BOT> hello", + text, user: "U1", - ts: "1700000000.0001", - event_ts: "1700000000.0001", + ts, + event_ts: ts, } as never, opts: { source: "message", wasMentioned: true }, }); + } + + it("prefixes channel bodies with sender label", async () => { + const ctx = createSenderPrefixCtx({ + channels: {}, + slashCommand: { command: "/openclaw", enabled: true }, + }); + + const result = await prepareSenderPrefixMessage(ctx, "<@BOT> hello", "1700000000.0001"); expect(result).not.toBeNull(); const body = result?.ctxPayload.Body ?? ""; @@ -502,20 +484,7 @@ describe("prepareSlackMessage sender prefix", () => { }, }); - const result = await prepareSlackMessage({ - ctx, - account: { accountId: "default", config: {} } as never, - message: { - type: "message", - channel: "C1", - channel_type: "channel", - text: "<@BOT> /new", - user: "U1", - ts: "1700000000.0002", - event_ts: "1700000000.0002", - } as never, - opts: { source: "message", wasMentioned: true }, - }); + const result = await prepareSenderPrefixMessage(ctx, "<@BOT> /new", "1700000000.0002"); expect(result).not.toBeNull(); expect(result?.ctxPayload.CommandAuthorized).toBe(true); diff --git a/src/telegram/bot/delivery.test.ts b/src/telegram/bot/delivery.test.ts index c1a008b1133..88c4e2a0c08 100644 --- a/src/telegram/bot/delivery.test.ts +++ b/src/telegram/bot/delivery.test.ts @@ -3,6 +3,14 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { deliverReplies } from "./delivery.js"; const loadWebMedia = vi.fn(); +const baseDeliveryParams = { + chatId: "123", + token: "tok", + replyToMode: "off", + textLimit: 4000, +} as const; +type DeliverRepliesParams = Parameters[0]; +type RuntimeStub = { error: ReturnType; log?: ReturnType }; vi.mock("../../web/media.js", () => ({ loadWebMedia: (...args: unknown[]) => loadWebMedia(...args), @@ -20,23 +28,41 @@ vi.mock("grammy", () => ({ }, })); +function createRuntime(withLog = true): RuntimeStub { + return withLog ? { error: vi.fn(), log: vi.fn() } : { error: vi.fn() }; +} + +function createBot(api: Record = {}): Bot { + return { api } as unknown as Bot; +} + +async function deliverWith(params: Omit) { + await deliverReplies({ + ...baseDeliveryParams, + ...params, + }); +} + +function mockMediaLoad(fileName: string, contentType: string, data: string) { + loadWebMedia.mockResolvedValueOnce({ + buffer: Buffer.from(data), + contentType, + fileName, + }); +} + describe("deliverReplies", () => { beforeEach(() => { loadWebMedia.mockReset(); }); it("skips audioAsVoice-only payloads without logging an error", async () => { - const runtime = { error: vi.fn() }; - const bot = { api: {} } as unknown as Bot; + const runtime = createRuntime(false); - await deliverReplies({ + await deliverWith({ replies: [{ audioAsVoice: true }], - chatId: "123", - token: "tok", runtime, - bot, - replyToMode: "off", - textLimit: 4000, + bot: createBot(), }); expect(runtime.error).not.toHaveBeenCalled(); @@ -44,30 +70,22 @@ describe("deliverReplies", () => { it("invokes onVoiceRecording before sending a voice note", async () => { const events: string[] = []; - const runtime = { error: vi.fn() }; + const runtime = createRuntime(false); const sendVoice = vi.fn(async () => { events.push("sendVoice"); return { message_id: 1, chat: { id: "123" } }; }); - const bot = { api: { sendVoice } } as unknown as Bot; + const bot = createBot({ sendVoice }); const onVoiceRecording = vi.fn(async () => { events.push("recordVoice"); }); - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("voice"), - contentType: "audio/ogg", - fileName: "note.ogg", - }); + mockMediaLoad("note.ogg", "audio/ogg", "voice"); - await deliverReplies({ + await deliverWith({ replies: [{ mediaUrl: "https://example.com/note.ogg", audioAsVoice: true }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, onVoiceRecording, }); @@ -77,27 +95,19 @@ describe("deliverReplies", () => { }); it("renders markdown in media captions", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendPhoto = vi.fn().mockResolvedValue({ message_id: 2, chat: { id: "123" }, }); - const bot = { api: { sendPhoto } } as unknown as Bot; + const bot = createBot({ sendPhoto }); - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("image"), - contentType: "image/jpeg", - fileName: "photo.jpg", - }); + mockMediaLoad("photo.jpg", "image/jpeg", "image"); - await deliverReplies({ + await deliverWith({ replies: [{ mediaUrl: "https://example.com/photo.jpg", text: "hi **boss**" }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, }); expect(sendPhoto).toHaveBeenCalledWith( @@ -111,29 +121,21 @@ describe("deliverReplies", () => { }); it("passes mediaLocalRoots to media loading", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendPhoto = vi.fn().mockResolvedValue({ message_id: 12, chat: { id: "123" }, }); - const bot = { api: { sendPhoto } } as unknown as Bot; + const bot = createBot({ sendPhoto }); const mediaLocalRoots = ["/tmp/workspace-work"]; - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("image"), - contentType: "image/jpeg", - fileName: "photo.jpg", - }); + mockMediaLoad("photo.jpg", "image/jpeg", "image"); - await deliverReplies({ + await deliverWith({ replies: [{ mediaUrl: "/tmp/workspace-work/photo.jpg" }], - chatId: "123", - token: "tok", runtime, bot, mediaLocalRoots, - replyToMode: "off", - textLimit: 4000, }); expect(loadWebMedia).toHaveBeenCalledWith("/tmp/workspace-work/photo.jpg", { @@ -142,21 +144,17 @@ describe("deliverReplies", () => { }); it("includes link_preview_options when linkPreview is false", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendMessage = vi.fn().mockResolvedValue({ message_id: 3, chat: { id: "123" }, }); - const bot = { api: { sendMessage } } as unknown as Bot; + const bot = createBot({ sendMessage }); - await deliverReplies({ + await deliverWith({ replies: [{ text: "Check https://example.com" }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, linkPreview: false, }); @@ -170,21 +168,17 @@ describe("deliverReplies", () => { }); it("does not include message_thread_id for DMs (threads don't exist in private chats)", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendMessage = vi.fn().mockResolvedValue({ message_id: 4, chat: { id: "123" }, }); - const bot = { api: { sendMessage } } as unknown as Bot; + const bot = createBot({ sendMessage }); - await deliverReplies({ + await deliverWith({ replies: [{ text: "Hello" }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, thread: { id: 1, scope: "dm" }, }); @@ -198,21 +192,17 @@ describe("deliverReplies", () => { }); it("does not include link_preview_options when linkPreview is true", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendMessage = vi.fn().mockResolvedValue({ message_id: 4, chat: { id: "123" }, }); - const bot = { api: { sendMessage } } as unknown as Bot; + const bot = createBot({ sendMessage }); - await deliverReplies({ + await deliverWith({ replies: [{ text: "Check https://example.com" }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, linkPreview: true, }); @@ -226,21 +216,18 @@ describe("deliverReplies", () => { }); it("uses reply_to_message_id when quote text is provided", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendMessage = vi.fn().mockResolvedValue({ message_id: 10, chat: { id: "123" }, }); - const bot = { api: { sendMessage } } as unknown as Bot; + const bot = createBot({ sendMessage }); - await deliverReplies({ + await deliverWith({ replies: [{ text: "Hello there", replyToId: "500" }], - chatId: "123", - token: "tok", runtime, bot, replyToMode: "all", - textLimit: 4000, replyQuoteText: "quoted text", }); @@ -261,7 +248,7 @@ describe("deliverReplies", () => { }); it("falls back to text when sendVoice fails with VOICE_MESSAGES_FORBIDDEN", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendVoice = vi .fn() .mockRejectedValue( @@ -273,24 +260,16 @@ describe("deliverReplies", () => { message_id: 5, chat: { id: "123" }, }); - const bot = { api: { sendVoice, sendMessage } } as unknown as Bot; + const bot = createBot({ sendVoice, sendMessage }); - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("voice"), - contentType: "audio/ogg", - fileName: "note.ogg", - }); + mockMediaLoad("note.ogg", "audio/ogg", "voice"); - await deliverReplies({ + await deliverWith({ replies: [ { mediaUrl: "https://example.com/note.ogg", text: "Hello there", audioAsVoice: true }, ], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, }); // Voice was attempted but failed @@ -305,26 +284,18 @@ describe("deliverReplies", () => { }); it("rethrows non-VOICE_MESSAGES_FORBIDDEN errors from sendVoice", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendVoice = vi.fn().mockRejectedValue(new Error("Network error")); const sendMessage = vi.fn(); - const bot = { api: { sendVoice, sendMessage } } as unknown as Bot; + const bot = createBot({ sendVoice, sendMessage }); - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("voice"), - contentType: "audio/ogg", - fileName: "note.ogg", - }); + mockMediaLoad("note.ogg", "audio/ogg", "voice"); await expect( - deliverReplies({ + deliverWith({ replies: [{ mediaUrl: "https://example.com/note.ogg", text: "Hello", audioAsVoice: true }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, }), ).rejects.toThrow("Network error"); @@ -334,7 +305,7 @@ describe("deliverReplies", () => { }); it("rethrows VOICE_MESSAGES_FORBIDDEN when no text fallback is available", async () => { - const runtime = { error: vi.fn(), log: vi.fn() }; + const runtime = createRuntime(); const sendVoice = vi .fn() .mockRejectedValue( @@ -343,23 +314,15 @@ describe("deliverReplies", () => { ), ); const sendMessage = vi.fn(); - const bot = { api: { sendVoice, sendMessage } } as unknown as Bot; + const bot = createBot({ sendVoice, sendMessage }); - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("voice"), - contentType: "audio/ogg", - fileName: "note.ogg", - }); + mockMediaLoad("note.ogg", "audio/ogg", "voice"); await expect( - deliverReplies({ + deliverWith({ replies: [{ mediaUrl: "https://example.com/note.ogg", audioAsVoice: true }], - chatId: "123", - token: "tok", runtime, bot, - replyToMode: "off", - textLimit: 4000, }), ).rejects.toThrow("VOICE_MESSAGES_FORBIDDEN"); diff --git a/src/web/auto-reply/web-auto-reply-monitor.test.ts b/src/web/auto-reply/web-auto-reply-monitor.test.ts index 766bd0f932e..f21d7e9e6b3 100644 --- a/src/web/auto-reply/web-auto-reply-monitor.test.ts +++ b/src/web/auto-reply/web-auto-reply-monitor.test.ts @@ -64,21 +64,36 @@ function runGroupGating(params: { return { result, groupHistories }; } +function createGroupMessage(overrides: Record = {}) { + return { + id: "g1", + from: "123@g.us", + conversationId: "123@g.us", + chatId: "123@g.us", + chatType: "group", + to: "+2", + body: "hello group", + senderE164: "+111", + senderName: "Alice", + selfE164: "+999", + sendComposing: async () => {}, + reply: async () => {}, + sendMedia: async () => {}, + ...overrides, + }; +} + describe("applyGroupGating", () => { it("treats reply-to-bot as implicit mention", () => { const cfg = makeConfig({}); const { result } = runGroupGating({ cfg, - msg: { + msg: createGroupMessage({ id: "m1", - from: "123@g.us", - conversationId: "123@g.us", to: "+15550000", accountId: "default", body: "following up", timestamp: Date.now(), - chatType: "group", - chatId: "123@g.us", selfJid: "15551234567@s.whatsapp.net", selfE164: "+15551234567", replyToId: "m0", @@ -86,10 +101,7 @@ describe("applyGroupGating", () => { replyToSender: "+15551234567", replyToSenderJid: "15551234567@s.whatsapp.net", replyToSenderE164: "+15551234567", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(result.shouldProcess).toBe(true); @@ -107,21 +119,12 @@ describe("applyGroupGating", () => { const { result } = runGroupGating({ cfg, - msg: { + msg: createGroupMessage({ id: "g-new", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", body: "/new", senderE164: "+111", senderName: "Owner", - selfE164: "+999", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(result.shouldProcess).toBe(true); @@ -139,21 +142,12 @@ describe("applyGroupGating", () => { const { result, groupHistories } = runGroupGating({ cfg, - msg: { + msg: createGroupMessage({ id: "g-new-unauth", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", body: "/new", senderE164: "+111", senderName: "NotOwner", - selfE164: "+999", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(result.shouldProcess).toBe(false); @@ -172,21 +166,12 @@ describe("applyGroupGating", () => { const { result } = runGroupGating({ cfg, - msg: { + msg: createGroupMessage({ id: "g-status", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", body: "/status", senderE164: "+111", senderName: "Owner", - selfE164: "+999", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(result.shouldProcess).toBe(true); @@ -232,42 +217,24 @@ describe("applyGroupGating", () => { const { result: globalMention } = runGroupGating({ cfg, agentId: route.agentId, - msg: { + msg: createGroupMessage({ id: "g1", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", body: "@global ping", senderE164: "+111", senderName: "Alice", - selfE164: "+999", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(globalMention.shouldProcess).toBe(false); const { result: workMention } = runGroupGating({ cfg, agentId: route.agentId, - msg: { + msg: createGroupMessage({ id: "g2", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", body: "@workbot ping", senderE164: "+222", senderName: "Bob", - selfE164: "+999", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(workMention.shouldProcess).toBe(true); }); @@ -285,21 +252,7 @@ describe("applyGroupGating", () => { const { result } = runGroupGating({ cfg, - msg: { - id: "g1", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", - body: "hello group", - senderE164: "+111", - senderName: "Alice", - selfE164: "+999", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + msg: createGroupMessage(), }); expect(result.shouldProcess).toBe(true); @@ -319,23 +272,11 @@ describe("applyGroupGating", () => { const { result } = runGroupGating({ cfg, - msg: { - id: "g1", - from: "123@g.us", - conversationId: "123@g.us", - chatId: "123@g.us", - chatType: "group", - to: "+2", + msg: createGroupMessage({ body: "@workbot ping", - senderE164: "+111", - senderName: "Alice", - selfE164: "+999", mentionedJids: ["999@s.whatsapp.net"], selfJid: "999@s.whatsapp.net", - sendComposing: async () => {}, - reply: async () => {}, - sendMedia: async () => {}, - }, + }), }); expect(result.shouldProcess).toBe(false); @@ -350,22 +291,15 @@ describe("buildInboundLine", () => { channels: { whatsapp: { messagePrefix: "" } }, } as never, agentId: "main", - msg: { - from: "123@g.us", - conversationId: "123@g.us", + msg: createGroupMessage({ to: "+15550009999", accountId: "default", body: "ping", timestamp: 1700000000000, - chatType: "group", - chatId: "123@g.us", senderJid: "111@s.whatsapp.net", senderE164: "+15550001111", senderName: "Bob", - sendComposing: async () => undefined, - reply: async () => undefined, - sendMedia: async () => undefined, - } as never, + }) as never, }); expect(line).toContain("Bob (+15550001111):");