diff --git a/src/line/bot-handlers.test.ts b/src/line/bot-handlers.test.ts index 39bfdf939..52b2174fa 100644 --- a/src/line/bot-handlers.test.ts +++ b/src/line/bot-handlers.test.ts @@ -68,6 +68,46 @@ let createLineWebhookReplayCache: typeof import("./bot-handlers.js").createLineW const createRuntime = () => ({ log: vi.fn(), error: vi.fn(), exit: vi.fn() }); +function createReplayMessageEvent(params: { + messageId: string; + groupId: string; + userId: string; + webhookEventId: string; + isRedelivery: boolean; +}) { + return { + type: "message", + message: { id: params.messageId, type: "text", text: "hello" }, + replyToken: "reply-token", + timestamp: Date.now(), + source: { type: "group", groupId: params.groupId, userId: params.userId }, + mode: "active", + webhookEventId: params.webhookEventId, + deliveryContext: { isRedelivery: params.isRedelivery }, + } as MessageEvent; +} + +function createOpenGroupReplayContext( + processMessage: ReturnType, + replayCache: ReturnType, +): Parameters[1] { + return { + cfg: { channels: { line: { groupPolicy: "open" } } }, + account: { + accountId: "default", + enabled: true, + channelAccessToken: "token", + channelSecret: "secret", + tokenSource: "config", + config: { groupPolicy: "open" }, + }, + runtime: createRuntime(), + mediaMaxBytes: 1, + processMessage, + replayCache, + }; +} + vi.mock("../pairing/pairing-store.js", () => ({ readChannelAllowFromStore: readAllowFromStoreMock, upsertChannelPairingRequest: upsertPairingRequestMock, @@ -377,32 +417,14 @@ describe("handleLineWebhookEvents", () => { it("deduplicates replayed webhook events by webhookEventId before processing", async () => { const processMessage = vi.fn(); - const event = { - type: "message", - message: { id: "m-replay", type: "text", text: "hello" }, - replyToken: "reply-token", - timestamp: Date.now(), - source: { type: "group", groupId: "group-replay", userId: "user-replay" }, - mode: "active", + const event = createReplayMessageEvent({ + messageId: "m-replay", + groupId: "group-replay", + userId: "user-replay", webhookEventId: "evt-replay-1", - deliveryContext: { isRedelivery: true }, - } as MessageEvent; - - const context: Parameters[1] = { - cfg: { channels: { line: { groupPolicy: "open" } } }, - account: { - accountId: "default", - enabled: true, - channelAccessToken: "token", - channelSecret: "secret", - tokenSource: "config", - config: { groupPolicy: "open" }, - }, - runtime: createRuntime(), - mediaMaxBytes: 1, - processMessage, - replayCache: createLineWebhookReplayCache(), - }; + isRedelivery: true, + }); + const context = createOpenGroupReplayContext(processMessage, createLineWebhookReplayCache()); await handleLineWebhookEvents([event], context); await handleLineWebhookEvents([event], context); @@ -419,32 +441,14 @@ describe("handleLineWebhookEvents", () => { const processMessage = vi.fn(async () => { await firstDone; }); - const event = { - type: "message", - message: { id: "m-inflight", type: "text", text: "hello" }, - replyToken: "reply-token", - timestamp: Date.now(), - source: { type: "group", groupId: "group-inflight", userId: "user-inflight" }, - mode: "active", + const event = createReplayMessageEvent({ + messageId: "m-inflight", + groupId: "group-inflight", + userId: "user-inflight", webhookEventId: "evt-inflight-1", - deliveryContext: { isRedelivery: true }, - } as MessageEvent; - - const context: Parameters[1] = { - cfg: { channels: { line: { groupPolicy: "open" } } }, - account: { - accountId: "default", - enabled: true, - channelAccessToken: "token", - channelSecret: "secret", - tokenSource: "config", - config: { groupPolicy: "open" }, - }, - runtime: createRuntime(), - mediaMaxBytes: 1, - processMessage, - replayCache: createLineWebhookReplayCache(), - }; + isRedelivery: true, + }); + const context = createOpenGroupReplayContext(processMessage, createLineWebhookReplayCache()); const firstRun = handleLineWebhookEvents([event], context); await Promise.resolve(); @@ -464,32 +468,14 @@ describe("handleLineWebhookEvents", () => { const processMessage = vi.fn(async () => { await firstDone; }); - const event = { - type: "message", - message: { id: "m-inflight-fail", type: "text", text: "hello" }, - replyToken: "reply-token", - timestamp: Date.now(), - source: { type: "group", groupId: "group-inflight", userId: "user-inflight" }, - mode: "active", + const event = createReplayMessageEvent({ + messageId: "m-inflight-fail", + groupId: "group-inflight", + userId: "user-inflight", webhookEventId: "evt-inflight-fail-1", - deliveryContext: { isRedelivery: true }, - } as MessageEvent; - - const context: Parameters[1] = { - cfg: { channels: { line: { groupPolicy: "open" } } }, - account: { - accountId: "default", - enabled: true, - channelAccessToken: "token", - channelSecret: "secret", - tokenSource: "config", - config: { groupPolicy: "open" }, - }, - runtime: createRuntime(), - mediaMaxBytes: 1, - processMessage, - replayCache: createLineWebhookReplayCache(), - }; + isRedelivery: true, + }); + const context = createOpenGroupReplayContext(processMessage, createLineWebhookReplayCache()); const firstRun = handleLineWebhookEvents([event], context); await Promise.resolve(); @@ -604,32 +590,14 @@ describe("handleLineWebhookEvents", () => { .fn() .mockRejectedValueOnce(new Error("transient failure")) .mockResolvedValueOnce(undefined); - const event = { - type: "message", - message: { id: "m-fail-then-retry", type: "text", text: "hello" }, - replyToken: "reply-token", - timestamp: Date.now(), - source: { type: "group", groupId: "group-retry", userId: "user-retry" }, - mode: "active", + const event = createReplayMessageEvent({ + messageId: "m-fail-then-retry", + groupId: "group-retry", + userId: "user-retry", webhookEventId: "evt-fail-then-retry", - deliveryContext: { isRedelivery: false }, - } as MessageEvent; - - const context: Parameters[1] = { - cfg: { channels: { line: { groupPolicy: "open" } } }, - account: { - accountId: "default", - enabled: true, - channelAccessToken: "token", - channelSecret: "secret", - tokenSource: "config", - config: { groupPolicy: "open" }, - }, - runtime: createRuntime(), - mediaMaxBytes: 1, - processMessage, - replayCache: createLineWebhookReplayCache(), - }; + isRedelivery: false, + }); + const context = createOpenGroupReplayContext(processMessage, createLineWebhookReplayCache()); await expect(handleLineWebhookEvents([event], context)).rejects.toThrow("transient failure"); await handleLineWebhookEvents([event], context);