mirror of https://github.com/openclaw/openclaw.git
test: share feishu schema and reaction assertions
This commit is contained in:
parent
a7e5925ec1
commit
7ca8804a33
|
|
@ -78,6 +78,25 @@ async function resolveReactionWithLookup(params: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveNonBotReaction(params?: { cfg?: ClawdbotConfig; uuid?: () => string }) {
|
||||||
|
return await resolveReactionSyntheticEvent({
|
||||||
|
cfg: params?.cfg ?? cfg,
|
||||||
|
accountId: "default",
|
||||||
|
event: makeReactionEvent(),
|
||||||
|
botOpenId: "ou_bot",
|
||||||
|
fetchMessage: async () => ({
|
||||||
|
messageId: "om_msg1",
|
||||||
|
chatId: "oc_group",
|
||||||
|
chatType: "group",
|
||||||
|
senderOpenId: "ou_other",
|
||||||
|
senderType: "user",
|
||||||
|
content: "hello",
|
||||||
|
contentType: "text",
|
||||||
|
}),
|
||||||
|
...(params?.uuid ? { uuid: params.uuid } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
type FeishuMention = NonNullable<FeishuMessageEvent["message"]["mentions"]>[number];
|
type FeishuMention = NonNullable<FeishuMessageEvent["message"]["mentions"]>[number];
|
||||||
|
|
||||||
function buildDebounceConfig(): ClawdbotConfig {
|
function buildDebounceConfig(): ClawdbotConfig {
|
||||||
|
|
@ -179,6 +198,19 @@ function getFirstDispatchedEvent(): FeishuMessageEvent {
|
||||||
return firstParams.event;
|
return firstParams.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectSingleDispatchedEvent(): FeishuMessageEvent {
|
||||||
|
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
||||||
|
return getFirstDispatchedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectParsedFirstDispatchedEvent(botOpenId = "ou_bot") {
|
||||||
|
const dispatched = expectSingleDispatchedEvent();
|
||||||
|
return {
|
||||||
|
dispatched,
|
||||||
|
parsed: parseFeishuMessageEvent(dispatched, botOpenId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setDedupPassThroughMocks(): void {
|
function setDedupPassThroughMocks(): void {
|
||||||
vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
||||||
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
||||||
|
|
@ -203,6 +235,13 @@ async function enqueueDebouncedMessage(
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setStaleRetryMocks(messageId = "om_old") {
|
||||||
|
vi.spyOn(dedup, "hasRecordedMessage").mockImplementation((key) => key.endsWith(`:${messageId}`));
|
||||||
|
vi.spyOn(dedup, "hasRecordedMessagePersistent").mockImplementation(
|
||||||
|
async (currentMessageId) => currentMessageId === messageId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
describe("resolveReactionSyntheticEvent", () => {
|
describe("resolveReactionSyntheticEvent", () => {
|
||||||
it("filters app self-reactions", async () => {
|
it("filters app self-reactions", async () => {
|
||||||
const event = makeReactionEvent({ operator_type: "app" });
|
const event = makeReactionEvent({ operator_type: "app" });
|
||||||
|
|
@ -262,28 +301,12 @@ describe("resolveReactionSyntheticEvent", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("filters reactions on non-bot messages", async () => {
|
it("filters reactions on non-bot messages", async () => {
|
||||||
const event = makeReactionEvent();
|
const result = await resolveNonBotReaction();
|
||||||
const result = await resolveReactionSyntheticEvent({
|
|
||||||
cfg,
|
|
||||||
accountId: "default",
|
|
||||||
event,
|
|
||||||
botOpenId: "ou_bot",
|
|
||||||
fetchMessage: async () => ({
|
|
||||||
messageId: "om_msg1",
|
|
||||||
chatId: "oc_group",
|
|
||||||
chatType: "group",
|
|
||||||
senderOpenId: "ou_other",
|
|
||||||
senderType: "user",
|
|
||||||
content: "hello",
|
|
||||||
contentType: "text",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows non-bot reactions when reactionNotifications is all", async () => {
|
it("allows non-bot reactions when reactionNotifications is all", async () => {
|
||||||
const event = makeReactionEvent();
|
const result = await resolveNonBotReaction({
|
||||||
const result = await resolveReactionSyntheticEvent({
|
|
||||||
cfg: {
|
cfg: {
|
||||||
channels: {
|
channels: {
|
||||||
feishu: {
|
feishu: {
|
||||||
|
|
@ -291,18 +314,6 @@ describe("resolveReactionSyntheticEvent", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ClawdbotConfig,
|
} as ClawdbotConfig,
|
||||||
accountId: "default",
|
|
||||||
event,
|
|
||||||
botOpenId: "ou_bot",
|
|
||||||
fetchMessage: async () => ({
|
|
||||||
messageId: "om_msg1",
|
|
||||||
chatId: "oc_group",
|
|
||||||
chatType: "group",
|
|
||||||
senderOpenId: "ou_other",
|
|
||||||
senderType: "user",
|
|
||||||
content: "hello",
|
|
||||||
contentType: "text",
|
|
||||||
}),
|
|
||||||
uuid: () => "fixed-uuid",
|
uuid: () => "fixed-uuid",
|
||||||
});
|
});
|
||||||
expect(result?.message.message_id).toBe("om_msg1:reaction:THUMBSUP:fixed-uuid");
|
expect(result?.message.message_id).toBe("om_msg1:reaction:THUMBSUP:fixed-uuid");
|
||||||
|
|
@ -457,8 +468,7 @@ describe("Feishu inbound debounce regressions", () => {
|
||||||
);
|
);
|
||||||
await vi.advanceTimersByTimeAsync(25);
|
await vi.advanceTimersByTimeAsync(25);
|
||||||
|
|
||||||
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
const dispatched = expectSingleDispatchedEvent();
|
||||||
const dispatched = getFirstDispatchedEvent();
|
|
||||||
const mergedMentions = dispatched.message.mentions ?? [];
|
const mergedMentions = dispatched.message.mentions ?? [];
|
||||||
expect(mergedMentions.some((mention) => mention.id.open_id === "ou_bot")).toBe(true);
|
expect(mergedMentions.some((mention) => mention.id.open_id === "ou_bot")).toBe(true);
|
||||||
expect(mergedMentions.some((mention) => mention.id.open_id === "ou_user_a")).toBe(false);
|
expect(mergedMentions.some((mention) => mention.id.open_id === "ou_user_a")).toBe(false);
|
||||||
|
|
@ -517,9 +527,7 @@ describe("Feishu inbound debounce regressions", () => {
|
||||||
);
|
);
|
||||||
await vi.advanceTimersByTimeAsync(25);
|
await vi.advanceTimersByTimeAsync(25);
|
||||||
|
|
||||||
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
const { dispatched, parsed } = expectParsedFirstDispatchedEvent();
|
||||||
const dispatched = getFirstDispatchedEvent();
|
|
||||||
const parsed = parseFeishuMessageEvent(dispatched, "ou_bot");
|
|
||||||
expect(parsed.mentionedBot).toBe(true);
|
expect(parsed.mentionedBot).toBe(true);
|
||||||
expect(parsed.mentionTargets).toBeUndefined();
|
expect(parsed.mentionTargets).toBeUndefined();
|
||||||
const mergedMentions = dispatched.message.mentions ?? [];
|
const mergedMentions = dispatched.message.mentions ?? [];
|
||||||
|
|
@ -547,19 +555,14 @@ describe("Feishu inbound debounce regressions", () => {
|
||||||
);
|
);
|
||||||
await vi.advanceTimersByTimeAsync(25);
|
await vi.advanceTimersByTimeAsync(25);
|
||||||
|
|
||||||
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
const { parsed } = expectParsedFirstDispatchedEvent();
|
||||||
const dispatched = getFirstDispatchedEvent();
|
|
||||||
const parsed = parseFeishuMessageEvent(dispatched, "ou_bot");
|
|
||||||
expect(parsed.mentionedBot).toBe(true);
|
expect(parsed.mentionedBot).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("excludes previously processed retries from combined debounce text", async () => {
|
it("excludes previously processed retries from combined debounce text", async () => {
|
||||||
vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
||||||
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
||||||
vi.spyOn(dedup, "hasRecordedMessage").mockImplementation((key) => key.endsWith(":om_old"));
|
setStaleRetryMocks();
|
||||||
vi.spyOn(dedup, "hasRecordedMessagePersistent").mockImplementation(
|
|
||||||
async (messageId) => messageId === "om_old",
|
|
||||||
);
|
|
||||||
const onMessage = await setupDebounceMonitor();
|
const onMessage = await setupDebounceMonitor();
|
||||||
|
|
||||||
await onMessage(createTextEvent({ messageId: "om_old", text: "stale" }));
|
await onMessage(createTextEvent({ messageId: "om_old", text: "stale" }));
|
||||||
|
|
@ -576,8 +579,7 @@ describe("Feishu inbound debounce regressions", () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
await vi.advanceTimersByTimeAsync(25);
|
await vi.advanceTimersByTimeAsync(25);
|
||||||
|
|
||||||
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
const dispatched = expectSingleDispatchedEvent();
|
||||||
const dispatched = getFirstDispatchedEvent();
|
|
||||||
expect(dispatched.message.message_id).toBe("om_new_2");
|
expect(dispatched.message.message_id).toBe("om_new_2");
|
||||||
const combined = JSON.parse(dispatched.message.content) as { text?: string };
|
const combined = JSON.parse(dispatched.message.content) as { text?: string };
|
||||||
expect(combined.text).toBe("first\nsecond");
|
expect(combined.text).toBe("first\nsecond");
|
||||||
|
|
@ -586,10 +588,7 @@ describe("Feishu inbound debounce regressions", () => {
|
||||||
it("uses latest fresh message id when debounce batch ends with stale retry", async () => {
|
it("uses latest fresh message id when debounce batch ends with stale retry", async () => {
|
||||||
const recordSpy = vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
const recordSpy = vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
||||||
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
||||||
vi.spyOn(dedup, "hasRecordedMessage").mockImplementation((key) => key.endsWith(":om_old"));
|
setStaleRetryMocks();
|
||||||
vi.spyOn(dedup, "hasRecordedMessagePersistent").mockImplementation(
|
|
||||||
async (messageId) => messageId === "om_old",
|
|
||||||
);
|
|
||||||
const onMessage = await setupDebounceMonitor();
|
const onMessage = await setupDebounceMonitor();
|
||||||
|
|
||||||
await onMessage(createTextEvent({ messageId: "om_new", text: "fresh" }));
|
await onMessage(createTextEvent({ messageId: "om_new", text: "fresh" }));
|
||||||
|
|
@ -600,8 +599,7 @@ describe("Feishu inbound debounce regressions", () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
await vi.advanceTimersByTimeAsync(25);
|
await vi.advanceTimersByTimeAsync(25);
|
||||||
|
|
||||||
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
const dispatched = expectSingleDispatchedEvent();
|
||||||
const dispatched = getFirstDispatchedEvent();
|
|
||||||
expect(dispatched.message.message_id).toBe("om_new");
|
expect(dispatched.message.message_id).toBe("om_new");
|
||||||
const combined = JSON.parse(dispatched.message.content) as { text?: string };
|
const combined = JSON.parse(dispatched.message.content) as { text?: string };
|
||||||
expect(combined.text).toBe("fresh");
|
expect(combined.text).toBe("fresh");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue