refactor(line): dedupe replay webhook test fixtures

This commit is contained in:
Peter Steinberger
2026-03-07 16:42:49 +00:00
parent 4575bbbb69
commit 5d37139ee5

View File

@@ -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<typeof vi.fn>,
replayCache: ReturnType<typeof createLineWebhookReplayCache>,
): Parameters<typeof handleLineWebhookEvents>[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<typeof handleLineWebhookEvents>[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<typeof handleLineWebhookEvents>[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<typeof handleLineWebhookEvents>[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<typeof handleLineWebhookEvents>[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);