mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 06:12:12 +00:00
refactor: share line webhook test helpers
This commit is contained in:
@@ -89,27 +89,73 @@ function createReplayMessageEvent(params: {
|
||||
} as MessageEvent;
|
||||
}
|
||||
|
||||
function createOpenGroupReplayContext(
|
||||
processMessage: LineWebhookContext["processMessage"],
|
||||
replayCache: ReturnType<typeof createLineWebhookReplayCache>,
|
||||
): Parameters<typeof handleLineWebhookEvents>[1] {
|
||||
function createTestMessageEvent(params: {
|
||||
message: MessageEvent["message"];
|
||||
source: MessageEvent["source"];
|
||||
webhookEventId: string;
|
||||
timestamp?: number;
|
||||
replyToken?: string;
|
||||
isRedelivery?: boolean;
|
||||
}) {
|
||||
return {
|
||||
cfg: { channels: { line: { groupPolicy: "open" } } },
|
||||
type: "message",
|
||||
message: params.message,
|
||||
replyToken: params.replyToken ?? "reply-token",
|
||||
timestamp: params.timestamp ?? Date.now(),
|
||||
source: params.source,
|
||||
mode: "active",
|
||||
webhookEventId: params.webhookEventId,
|
||||
deliveryContext: { isRedelivery: params.isRedelivery ?? false },
|
||||
} as MessageEvent;
|
||||
}
|
||||
|
||||
function createLineWebhookTestContext(params: {
|
||||
processMessage: LineWebhookContext["processMessage"];
|
||||
groupPolicy?: "open";
|
||||
dmPolicy?: "open";
|
||||
requireMention?: boolean;
|
||||
groupHistories?: Map<string, import("../auto-reply/reply/history.js").HistoryEntry[]>;
|
||||
replayCache?: ReturnType<typeof createLineWebhookReplayCache>;
|
||||
}): Parameters<typeof handleLineWebhookEvents>[1] {
|
||||
const lineConfig = {
|
||||
...(params.groupPolicy ? { groupPolicy: params.groupPolicy } : {}),
|
||||
...(params.dmPolicy ? { dmPolicy: params.dmPolicy } : {}),
|
||||
};
|
||||
return {
|
||||
cfg: { channels: { line: lineConfig } },
|
||||
account: {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
tokenSource: "config",
|
||||
config: { groupPolicy: "open", groups: { "*": { requireMention: false } } },
|
||||
config: {
|
||||
...lineConfig,
|
||||
...(params.requireMention === undefined
|
||||
? {}
|
||||
: { groups: { "*": { requireMention: params.requireMention } } }),
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
replayCache,
|
||||
processMessage: params.processMessage,
|
||||
...(params.groupHistories ? { groupHistories: params.groupHistories } : {}),
|
||||
...(params.replayCache ? { replayCache: params.replayCache } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function createOpenGroupReplayContext(
|
||||
processMessage: LineWebhookContext["processMessage"],
|
||||
replayCache: ReturnType<typeof createLineWebhookReplayCache>,
|
||||
): Parameters<typeof handleLineWebhookEvents>[1] {
|
||||
return createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
requireMention: false,
|
||||
replayCache,
|
||||
});
|
||||
}
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readChannelAllowFromStore: readAllowFromStoreMock,
|
||||
upsertChannelPairingRequest: upsertPairingRequestMock,
|
||||
@@ -631,32 +677,20 @@ describe("handleLineWebhookEvents", () => {
|
||||
|
||||
it("skips group messages by default when requireMention is not configured", async () => {
|
||||
const processMessage = vi.fn();
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: { id: "m-default-skip", type: "text", text: "hi there" },
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
source: { type: "group", groupId: "group-default", userId: "user-default" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-default-skip",
|
||||
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" },
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(processMessage).not.toHaveBeenCalled();
|
||||
expect(buildLineMessageContextMock).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -667,33 +701,22 @@ describe("handleLineWebhookEvents", () => {
|
||||
string,
|
||||
import("../auto-reply/reply/history.js").HistoryEntry[]
|
||||
>();
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: { id: "m-hist-1", type: "text", text: "hello history" },
|
||||
replyToken: "reply-token",
|
||||
timestamp: 1700000000000,
|
||||
source: { type: "group", groupId: "group-hist-1", userId: "user-hist" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-hist-1",
|
||||
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" },
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
groupHistories,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
groupHistories,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(processMessage).not.toHaveBeenCalled();
|
||||
const entries = groupHistories.get("group-hist-1");
|
||||
expect(entries).toHaveLength(1);
|
||||
@@ -706,35 +729,21 @@ describe("handleLineWebhookEvents", () => {
|
||||
|
||||
it("skips group messages without mention when requireMention is set", async () => {
|
||||
const processMessage = vi.fn();
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: { id: "m-mention-1", type: "text", text: "hi there" },
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
source: { type: "group", groupId: "group-mention", userId: "user-mention" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-mention-1",
|
||||
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: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(processMessage).not.toHaveBeenCalled();
|
||||
expect(buildLineMessageContextMock).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -742,8 +751,7 @@ describe("handleLineWebhookEvents", () => {
|
||||
it("processes group messages with bot mention when requireMention is set", async () => {
|
||||
const processMessage = vi.fn();
|
||||
// Simulate a LINE text message with mention.mentionees containing isSelf=true
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: {
|
||||
id: "m-mention-2",
|
||||
type: "text",
|
||||
@@ -751,41 +759,27 @@ describe("handleLineWebhookEvents", () => {
|
||||
mention: {
|
||||
mentionees: [{ index: 0, length: 4, type: "user", isSelf: true }],
|
||||
},
|
||||
},
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
} as unknown as MessageEvent["message"],
|
||||
source: { type: "group", groupId: "group-mention", userId: "user-mention" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-mention-2",
|
||||
deliveryContext: { isRedelivery: false },
|
||||
} as unknown 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: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(buildLineMessageContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(processMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("processes group messages with @all mention when requireMention is set", async () => {
|
||||
const processMessage = vi.fn();
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: {
|
||||
id: "m-mention-3",
|
||||
type: "text",
|
||||
@@ -793,68 +787,41 @@ describe("handleLineWebhookEvents", () => {
|
||||
mention: {
|
||||
mentionees: [{ index: 0, length: 4, type: "all" }],
|
||||
},
|
||||
},
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
} as MessageEvent["message"],
|
||||
source: { type: "group", groupId: "group-mention", userId: "user-mention" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-mention-3",
|
||||
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: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(buildLineMessageContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(processMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not apply requireMention gating to DM messages", async () => {
|
||||
const processMessage = vi.fn();
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: { id: "m-mention-dm", type: "text", text: "hi" },
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
source: { type: "user", userId: "user-dm" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-mention-dm",
|
||||
deliveryContext: { isRedelivery: false },
|
||||
} as MessageEvent;
|
||||
|
||||
await handleLineWebhookEvents([event], {
|
||||
cfg: { channels: { line: { dmPolicy: "open" } } },
|
||||
account: {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
tokenSource: "config",
|
||||
config: {
|
||||
dmPolicy: "open",
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
dmPolicy: "open",
|
||||
requireMention: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(buildLineMessageContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(processMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -862,35 +829,21 @@ describe("handleLineWebhookEvents", () => {
|
||||
it("allows non-text group messages through when requireMention is set (cannot detect mention)", async () => {
|
||||
const processMessage = vi.fn();
|
||||
// Image message -- LINE only carries mention metadata on text messages.
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: { id: "m-mention-img", type: "image", contentProvider: { type: "line" } },
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
source: { type: "group", groupId: "group-1", userId: "user-img" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-mention-img",
|
||||
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: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(buildLineMessageContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(processMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -898,40 +851,26 @@ describe("handleLineWebhookEvents", () => {
|
||||
it("does not bypass mention gating when non-bot mention is present with control command", async () => {
|
||||
const processMessage = vi.fn();
|
||||
// Text message mentions another user (not bot) together with a control command.
|
||||
const event = {
|
||||
type: "message",
|
||||
const event = createTestMessageEvent({
|
||||
message: {
|
||||
id: "m-mention-other",
|
||||
type: "text",
|
||||
text: "@other !status",
|
||||
mention: { mentionees: [{ index: 0, length: 6, type: "user", isSelf: false }] },
|
||||
},
|
||||
replyToken: "reply-token",
|
||||
timestamp: Date.now(),
|
||||
} as unknown as MessageEvent["message"],
|
||||
source: { type: "group", groupId: "group-1", userId: "user-other" },
|
||||
mode: "active",
|
||||
webhookEventId: "evt-mention-other",
|
||||
deliveryContext: { isRedelivery: false },
|
||||
} as unknown 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: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
runtime: createRuntime(),
|
||||
mediaMaxBytes: 1,
|
||||
processMessage,
|
||||
});
|
||||
|
||||
await handleLineWebhookEvents(
|
||||
[event],
|
||||
createLineWebhookTestContext({
|
||||
processMessage,
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Should be skipped because there is a non-bot mention and the bot was not mentioned.
|
||||
expect(processMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user