feat(handler): implement card callback response with toast and updated card
Add visual feedback for card actions by returning toast messages and updated cards per Feishu requirements. The handler now responds within 3 seconds with appropriate success/warning toasts and card updates showing approval/denial status. Also added operator tracking in logs and comprehensive tests.
This commit is contained in:
@@ -783,6 +783,91 @@ class TestBuildApprovalCard:
|
||||
assert action_el["actions"][1]["value"]["action"] == "deny"
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 8b. bot/handler.py card callback response
|
||||
# ===========================================================================
|
||||
|
||||
|
||||
class TestCardCallbackResponse:
|
||||
"""Test _handle_card_action returns proper callback response per
|
||||
docs/feishu/card_callback_communication.md."""
|
||||
|
||||
def _make_card_event(self, action: str, conv_id: str) -> object:
|
||||
"""Create a mock CustomizedEvent with card action payload."""
|
||||
import lark_oapi as lark
|
||||
mock_event = MagicMock()
|
||||
payload = json.dumps({
|
||||
"schema": "2.0",
|
||||
"header": {
|
||||
"event_id": "evt_123",
|
||||
"event_type": "card.action.trigger",
|
||||
},
|
||||
"event": {
|
||||
"operator": {
|
||||
"open_id": "ou_test_user_123",
|
||||
},
|
||||
"action": {
|
||||
"value": {"action": action, "conv_id": conv_id},
|
||||
"tag": "button",
|
||||
},
|
||||
},
|
||||
})
|
||||
# lark.JSON.marshal returns the JSON string
|
||||
with patch("lark_oapi.JSON.marshal", return_value=payload):
|
||||
yield payload
|
||||
|
||||
def test_approve_returns_toast_and_card(self):
|
||||
from bot.handler import _handle_card_action
|
||||
payload = json.dumps({
|
||||
"event": {
|
||||
"operator": {"open_id": "ou_test"},
|
||||
"action": {"value": {"action": "approve", "conv_id": "c1"}, "tag": "button"},
|
||||
},
|
||||
})
|
||||
with patch("lark_oapi.JSON.marshal", return_value=payload), \
|
||||
patch("bot.handler._main_loop", new=MagicMock()):
|
||||
result = _handle_card_action(MagicMock())
|
||||
|
||||
assert result is not None
|
||||
# Toast
|
||||
assert result["toast"]["type"] == "success"
|
||||
assert "批准" in result["toast"]["content"]
|
||||
# Updated card
|
||||
assert result["card"]["type"] == "raw"
|
||||
card_data = result["card"]["data"]
|
||||
assert card_data["schema"] == "2.0"
|
||||
assert card_data["header"]["template"] == "green"
|
||||
assert "批准" in card_data["body"]["elements"][0]["content"]
|
||||
|
||||
def test_deny_returns_warning_toast(self):
|
||||
from bot.handler import _handle_card_action
|
||||
payload = json.dumps({
|
||||
"event": {
|
||||
"operator": {"open_id": "ou_test"},
|
||||
"action": {"value": {"action": "deny", "conv_id": "c1"}, "tag": "button"},
|
||||
},
|
||||
})
|
||||
with patch("lark_oapi.JSON.marshal", return_value=payload), \
|
||||
patch("bot.handler._main_loop", new=MagicMock()):
|
||||
result = _handle_card_action(MagicMock())
|
||||
|
||||
assert result is not None
|
||||
assert result["toast"]["type"] == "warning"
|
||||
assert "拒绝" in result["toast"]["content"]
|
||||
assert result["card"]["data"]["header"]["template"] == "red"
|
||||
|
||||
def test_missing_value_returns_none(self):
|
||||
from bot.handler import _handle_card_action
|
||||
payload = json.dumps({
|
||||
"event": {
|
||||
"action": {"value": {}, "tag": "button"},
|
||||
},
|
||||
})
|
||||
with patch("lark_oapi.JSON.marshal", return_value=payload):
|
||||
result = _handle_card_action(MagicMock())
|
||||
assert result is None
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 9. Permission mode constants
|
||||
# ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user