import type { MessageEvent } from "@line/bot-sdk"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const { buildLineMessageContextMock, buildLinePostbackContextMock } = vi.hoisted(() => ({ buildLineMessageContextMock: vi.fn(async () => ({ ctxPayload: { From: "line:group:group-1" }, replyToken: "reply-token", route: { agentId: "default" }, isGroup: true, accountId: "default", })), buildLinePostbackContextMock: vi.fn(async () => null), })); vi.mock("./bot-message-context.js", () => ({ buildLineMessageContext: (...args: unknown[]) => buildLineMessageContextMock(...args), buildLinePostbackContext: (...args: unknown[]) => buildLinePostbackContextMock(...args), })); const { readAllowFromStoreMock, upsertPairingRequestMock } = vi.hoisted(() => ({ readAllowFromStoreMock: vi.fn(async () => [] as string[]), upsertPairingRequestMock: vi.fn(async () => ({ code: "CODE", created: true })), })); let handleLineWebhookEvents: typeof import("./bot-handlers.js").handleLineWebhookEvents; vi.mock("../pairing/pairing-store.js", () => ({ readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), })); describe("handleLineWebhookEvents", () => { beforeAll(async () => { ({ handleLineWebhookEvents } = await import("./bot-handlers.js")); }); beforeEach(() => { buildLineMessageContextMock.mockClear(); buildLinePostbackContextMock.mockClear(); readAllowFromStoreMock.mockClear(); upsertPairingRequestMock.mockClear(); }); it("blocks group messages when groupPolicy is disabled", async () => { const processMessage = vi.fn(); const event = { type: "message", message: { id: "m1", type: "text", text: "hi" }, replyToken: "reply-token", timestamp: Date.now(), source: { type: "group", groupId: "group-1", userId: "user-1" }, mode: "active", webhookEventId: "evt-1", deliveryContext: { isRedelivery: false }, } as MessageEvent; await handleLineWebhookEvents([event], { cfg: { channels: { line: { groupPolicy: "disabled" } } }, account: { accountId: "default", enabled: true, channelAccessToken: "token", channelSecret: "secret", tokenSource: "config", config: { groupPolicy: "disabled" }, }, runtime: { error: vi.fn() }, mediaMaxBytes: 1, processMessage, }); expect(processMessage).not.toHaveBeenCalled(); expect(buildLineMessageContextMock).not.toHaveBeenCalled(); }); it("blocks group messages when allowlist is empty", async () => { const processMessage = vi.fn(); const event = { type: "message", message: { id: "m2", type: "text", text: "hi" }, replyToken: "reply-token", timestamp: Date.now(), source: { type: "group", groupId: "group-1", userId: "user-2" }, mode: "active", webhookEventId: "evt-2", deliveryContext: { isRedelivery: false }, } as MessageEvent; await handleLineWebhookEvents([event], { cfg: { channels: { line: { groupPolicy: "allowlist" } } }, account: { accountId: "default", enabled: true, channelAccessToken: "token", channelSecret: "secret", tokenSource: "config", config: { groupPolicy: "allowlist" }, }, runtime: { error: vi.fn() }, mediaMaxBytes: 1, processMessage, }); expect(processMessage).not.toHaveBeenCalled(); expect(buildLineMessageContextMock).not.toHaveBeenCalled(); }); it("allows group messages when sender is in groupAllowFrom", async () => { const processMessage = vi.fn(); const event = { type: "message", message: { id: "m3", type: "text", text: "hi" }, replyToken: "reply-token", timestamp: Date.now(), source: { type: "group", groupId: "group-1", userId: "user-3" }, mode: "active", webhookEventId: "evt-3", deliveryContext: { isRedelivery: false }, } as MessageEvent; await handleLineWebhookEvents([event], { cfg: { channels: { line: { groupPolicy: "allowlist", groupAllowFrom: ["user-3"] } }, }, account: { accountId: "default", enabled: true, channelAccessToken: "token", channelSecret: "secret", tokenSource: "config", config: { groupPolicy: "allowlist", groupAllowFrom: ["user-3"] }, }, runtime: { error: vi.fn() }, mediaMaxBytes: 1, processMessage, }); expect(buildLineMessageContextMock).toHaveBeenCalledTimes(1); expect(processMessage).toHaveBeenCalledTimes(1); }); it("blocks group messages when wildcard group config disables groups", async () => { const processMessage = vi.fn(); const event = { type: "message", message: { id: "m4", type: "text", text: "hi" }, replyToken: "reply-token", timestamp: Date.now(), source: { type: "group", groupId: "group-2", userId: "user-4" }, mode: "active", webhookEventId: "evt-4", deliveryContext: { isRedelivery: false }, } as MessageEvent; await handleLineWebhookEvents([event], { cfg: { channels: { line: { groupPolicy: "open" } } }, account: { accountId: "default", enabled: true, channelAccessToken: "token", channelSecret: "secret", tokenSource: "config", config: { groupPolicy: "open", groups: { "*": { enabled: false } } }, }, runtime: { error: vi.fn() }, mediaMaxBytes: 1, processMessage, }); expect(processMessage).not.toHaveBeenCalled(); expect(buildLineMessageContextMock).not.toHaveBeenCalled(); }); });