import json import threading import time from types import SimpleNamespace from unittest.mock import MagicMock from bridge.context import Context, ContextType from channel.chat_channel import ChatChannel from channel.feishu import feishu_channel from channel.feishu.feishu_channel import FeishuController, FeiShuChanel from common.dequeue import Dequeue from common.expired_dict import ExpiredDict def _context(message_id: str) -> Context: return Context( ContextType.TEXT, message_id, { "session_id": "session-1", "msg": SimpleNamespace(msg_id=message_id), }, ) def _bare_chat_channel(*contexts: Context) -> ChatChannel: channel = ChatChannel.__new__(ChatChannel) channel.lock = threading.RLock() channel.futures = {} queue = Dequeue() for context in contexts: queue.put(context) channel.sessions = {"session-1": [queue, MagicMock()]} return channel def test_cancel_message_removes_only_recalled_queued_context(monkeypatch): channel = _bare_chat_channel(_context("m1"), _context("m2"), _context("m3")) registry = MagicMock() registry.cancel_request.return_value = False monkeypatch.setattr("agent.protocol.get_cancel_registry", lambda: registry) queued, active = channel.cancel_message("session-1", "m2") assert (queued, active) == (1, False) remaining = channel.sessions["session-1"][0] assert [remaining.get_nowait().get("msg").msg_id for _ in range(2)] == ["m1", "m3"] registry.cancel_request.assert_called_once_with("m2") def test_cancel_message_targets_active_request_without_clearing_queue(monkeypatch): channel = _bare_chat_channel(_context("later")) registry = MagicMock() registry.cancel_request.return_value = True monkeypatch.setattr("agent.protocol.get_cancel_registry", lambda: registry) queued, active = channel.cancel_message("session-1", "active") assert (queued, active) == (0, True) remaining = channel.sessions["session-1"][0] assert remaining.get_nowait().get("msg").msg_id == "later" def test_feishu_message_uses_message_id_for_precise_recall(monkeypatch): channel = FeiShuChanel() channel.receivedMsgs = ExpiredDict(60) channel._message_sessions = ExpiredDict(60) monkeypatch.setattr(channel, "fetch_access_token", lambda: "tenant-token") monkeypatch.setattr(channel, "_make_feishu_stream_callback", lambda *_: MagicMock()) produced = [] monkeypatch.setattr(channel, "produce", produced.append) channel._handle_message_event( { "app_id": "cli_bot", "sender": {"sender_id": {"open_id": "ou_user"}}, "message": { "message_id": "om_recall_me", "chat_id": "oc_chat", "chat_type": "p2p", "message_type": "text", "create_time": str(int(time.time() * 1000)), "content": json.dumps({"text": "long task"}), }, } ) assert len(produced) == 1 assert produced[0]["request_id"] == "om_recall_me" assert channel._message_sessions.get("om_recall_me") == "ou_user" def test_feishu_recall_cancels_only_the_original_message(monkeypatch): channel = FeiShuChanel() channel._message_sessions = ExpiredDict(60) channel._message_sessions["om_recalled"] = "session-1" cancel_message = MagicMock(return_value=(0, True)) monkeypatch.setattr(channel, "cancel_message", cancel_message) result = channel._handle_message_recalled_event( {"message_id": "om_recalled", "chat_id": "oc_chat"} ) assert result == (0, True) cancel_message.assert_called_once_with("session-1", "om_recalled") assert channel._message_sessions.get("om_recalled") is None def test_feishu_recall_ignores_unknown_message(): channel = FeiShuChanel() channel._message_sessions = ExpiredDict(60) assert channel._handle_message_recalled_event({"message_id": "unknown"}) == (0, False) def test_feishu_webhook_routes_message_recall(monkeypatch): channel = FeiShuChanel() channel.feishu_token = "verification-token" handle_recall = MagicMock(return_value=(1, False)) monkeypatch.setattr(channel, "_handle_message_recalled_event", handle_recall) event = {"message_id": "om_recalled", "chat_id": "oc_chat"} request = { "header": { "event_type": "im.message.recalled_v1", "token": "verification-token", }, "event": event, } monkeypatch.setattr( feishu_channel.web, "data", lambda: json.dumps(request).encode("utf-8"), ) assert json.loads(FeishuController().POST()) == {"success": True} handle_recall.assert_called_once_with(event)