test(feishu): assert typing cleanup on dispatch paths (#27640)

This commit is contained in:
Peter Steinberger
2026-02-26 16:56:55 +01:00
parent fb6012cb6a
commit 6396faa2c3
2 changed files with 82 additions and 5 deletions

View File

@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
- Feishu/Doc tools: route `feishu_doc` and `feishu_app_scopes` through the active agent account context (with explicit `accountId` override support) so multi-account agents no longer default to the first configured app, with regression coverage for context routing and explicit override behavior. (#27338) thanks @AaronL725.
- Feishu/Inbound message metadata: include inbound `message_id` in `BodyForAgent` on a dedicated metadata line so agents can reliably correlate and act on media/message operations that require message IDs, with regression coverage. (#27253) thanks @xss925175263.
- Feishu/Permission error dispatch: merge sender-name permission notices into the main inbound dispatch so one user message produces one agent turn/reply (instead of a duplicate permission-notice turn), with regression coverage. (#27381) thanks @byungsker.
- Feishu/Typing cleanup: always call dispatcher completion/idle cleanup in `finally` after Feishu dispatch, so typing indicator keepalive loops stop on both success and error paths; added regression coverage for cleanup on both paths. (#27640) thanks @kevinWangSheng.
- Telegram/Inline buttons: allow callback-query button handling in groups (including `/models` follow-up buttons) when group policy authorizes the sender, by removing the redundant callback allowlist gate that blocked open-policy groups. (#27343) Thanks @GodsBoy.
- Telegram/Streaming preview: when finalizing without an existing preview message, prime pending preview text with final answer before stop-flush so users do not briefly see stale 1-2 word fragments (for example `no` before `no problem`). (#27449) Thanks @emanuelst for the original fix direction in #19673.
- Telegram/sendChatAction 401 handling: add bounded exponential backoff + temporary local typing suppression after repeated unauthorized failures to stop unbounded `sendChatAction` retry loops that can trigger Telegram abuse enforcement and bot deletion. (#27415) Thanks @widingmarcus-cyber.

View File

@@ -11,11 +11,14 @@ const {
mockDownloadMessageResourceFeishu,
mockCreateFeishuClient,
} = vi.hoisted(() => ({
mockCreateFeishuReplyDispatcher: vi.fn(() => ({
dispatcher: vi.fn(),
replyOptions: {},
markDispatchIdle: vi.fn(),
})),
mockCreateFeishuReplyDispatcher: vi.fn(() => {
const markComplete = vi.fn();
return {
dispatcher: { markComplete },
replyOptions: {},
markDispatchIdle: vi.fn(),
};
}),
mockSendMessageFeishu: vi.fn().mockResolvedValue({ messageId: "pairing-msg", chatId: "oc-dm" }),
mockGetMessageFeishu: vi.fn().mockResolvedValue(null),
mockDownloadMessageResourceFeishu: vi.fn().mockResolvedValue({
@@ -517,4 +520,77 @@ describe("handleFeishuMessage command authorization", () => {
}),
);
});
it("always marks dispatcher complete and dispatch idle after successful dispatch", async () => {
mockShouldComputeCommandAuthorized.mockReturnValue(false);
const cfg: ClawdbotConfig = {
channels: {
feishu: {
dmPolicy: "open",
},
},
} as ClawdbotConfig;
const event: FeishuMessageEvent = {
sender: {
sender_id: {
open_id: "ou-success",
},
},
message: {
message_id: "msg-success-cleanup",
chat_id: "oc-dm",
chat_type: "p2p",
message_type: "text",
content: JSON.stringify({ text: "hello" }),
},
};
await dispatchMessage({ cfg, event });
const dispatcherResult = mockCreateFeishuReplyDispatcher.mock.results.at(-1)?.value as
| {
dispatcher: { markComplete: ReturnType<typeof vi.fn> };
markDispatchIdle: ReturnType<typeof vi.fn>;
}
| undefined;
expect(dispatcherResult?.dispatcher.markComplete).toHaveBeenCalledTimes(1);
expect(dispatcherResult?.markDispatchIdle).toHaveBeenCalledTimes(1);
});
it("marks dispatcher complete and dispatch idle when dispatch throws", async () => {
mockShouldComputeCommandAuthorized.mockReturnValue(false);
mockDispatchReplyFromConfig.mockRejectedValueOnce(new Error("dispatch boom"));
const cfg: ClawdbotConfig = {
channels: {
feishu: {
dmPolicy: "open",
},
},
} as ClawdbotConfig;
const event: FeishuMessageEvent = {
sender: {
sender_id: {
open_id: "ou-fail",
},
},
message: {
message_id: "msg-error-cleanup",
chat_id: "oc-dm",
chat_type: "p2p",
message_type: "text",
content: JSON.stringify({ text: "hello" }),
},
};
await dispatchMessage({ cfg, event });
const dispatcherResult = mockCreateFeishuReplyDispatcher.mock.results.at(-1)?.value as
| {
dispatcher: { markComplete: ReturnType<typeof vi.fn> };
markDispatchIdle: ReturnType<typeof vi.fn>;
}
| undefined;
expect(dispatcherResult?.dispatcher.markComplete).toHaveBeenCalledTimes(1);
expect(dispatcherResult?.markDispatchIdle).toHaveBeenCalledTimes(1);
});
});