diff --git a/tests/tools/test_send_message_target_parse.py b/tests/tools/test_send_message_target_parse.py index 36f75f1291..3c5d3482ee 100644 --- a/tests/tools/test_send_message_target_parse.py +++ b/tests/tools/test_send_message_target_parse.py @@ -141,6 +141,78 @@ def test_live_buzz_media_failure_is_explicit_not_omitted(tmp_path) -> None: assert "media_delivered" not in result +def test_live_buzz_media_only_send_reaches_adapter(tmp_path) -> None: + from gateway.platforms.base import SendResult + + platform = Platform("buzz") + document = tmp_path / "report.txt" + document.write_text("report", encoding="utf-8") + media_calls = [] + + class Adapter: + async def send(self, **kwargs): + raise AssertionError("media-only send must not emit an empty text message") + + async def send_document(self, chat_id, file_path, **kwargs): + media_calls.append((chat_id, file_path, kwargs)) + return SendResult(success=True, message_id="evt-document") + + runner = SimpleNamespace(adapters={platform: Adapter()}) + with patch("gateway.run._gateway_runner_ref", return_value=runner): + result = asyncio.run( + _send_to_platform( + platform, + SimpleNamespace(enabled=True, token=None, extra={}), + "31b543d5-80d4-4df5-8a5c-cefca1a58fdd", + "", + media_files=[(str(document), False)], + ) + ) + + assert result == { + "success": True, + "message_id": "evt-document", + "media_delivered": True, + } + assert len(media_calls) == 1 + assert media_calls[0][1] == str(document) + + +def test_live_buzz_media_exception_reports_partial_delivery(tmp_path) -> None: + from gateway.platforms.base import SendResult + + platform = Platform("buzz") + first = tmp_path / "first.txt" + second = tmp_path / "second.txt" + first.write_text("first", encoding="utf-8") + second.write_text("second", encoding="utf-8") + + class Adapter: + async def send(self, **kwargs): + return SendResult(success=True, message_id="evt-text") + + async def send_document(self, chat_id, file_path, **kwargs): + if file_path == str(second): + raise RuntimeError("relay transport failed") + return SendResult(success=True, message_id="evt-first") + + runner = SimpleNamespace(adapters={platform: Adapter()}) + with patch("gateway.run._gateway_runner_ref", return_value=runner): + result = asyncio.run( + _send_to_platform( + platform, + SimpleNamespace(enabled=True, token=None, extra={}), + "31b543d5-80d4-4df5-8a5c-cefca1a58fdd", + "attached files", + media_files=[(str(first), False), (str(second), False)], + ) + ) + + assert "after 1/2 files" in result["error"] + assert "relay transport failed" in result["error"] + assert "media_delivered" not in result + + def test_live_adapter_inherited_media_fallback_is_not_claimed_as_delivery(tmp_path) -> None: from gateway.platforms.base import BasePlatformAdapter, SendResult diff --git a/tools/send_message_tool.py b/tools/send_message_tool.py index dfc9c40be6..11a7bf40c2 100644 --- a/tools/send_message_tool.py +++ b/tools/send_message_tool.py @@ -917,7 +917,17 @@ async def _send_live_adapter_media( f"media file {index + 1}/{total} was not sent" ) } - last_result = await getattr(adapter, method_name)(chat_id, media_path, **kwargs) + try: + last_result = await getattr(adapter, method_name)(chat_id, media_path, **kwargs) + except asyncio.CancelledError: + raise + except Exception as exc: + return { + "error": ( + f"Adapter media send failed after {index}/{total} files: " + f"{_bounded_send_error(exc)}" + ) + } if not last_result.success: detail = _bounded_send_error(last_result.error or "media send failed") return { @@ -1456,7 +1466,9 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None, return last_result # --- Non-media platforms --- - if media_files and not message.strip(): + # Buzz is a plugin platform with verified native media delivery through + # _send_via_adapter below, including valid media-only sends. + if media_files and not message.strip() and platform.value != "buzz": return { "error": ( f"send_message MEDIA delivery is currently only supported for telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp and slack; " @@ -1464,7 +1476,7 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None, ) } warning = None - if media_files: + if media_files and platform.value != "buzz": warning = ( f"MEDIA attachments were omitted for {platform.value}; " "native send_message media delivery is currently only supported for telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp and slack"