fix(weixin): raise exceptions instead of silently dropping messages

_send_text() swallowed API errors (non-zero errcode) with just a
warning log, and send() had three silent return paths (no client,
session paused, no context_token). Neither triggered ChannelManager's
retry logic, causing persistent message loss until a new inbound
message refreshed the context_token.

Now all failure paths raise RuntimeError, matching BaseChannel's
contract and enabling proper retry behavior.
This commit is contained in:
chengyongru
2026-05-06 23:52:50 +08:00
committed by Xubin Ren
parent 4efd904ccc
commit 98c2f7cc27
2 changed files with 51 additions and 22 deletions
+45 -8
View File
@@ -319,21 +319,22 @@ async def test_process_message_does_not_fallback_when_top_level_media_exists_but
@pytest.mark.asyncio
async def test_send_without_context_token_does_not_send_text() -> None:
async def test_send_without_context_token_raises() -> None:
channel, _bus = _make_channel()
channel._client = object()
channel._token = "token"
channel._send_text = AsyncMock()
await channel.send(
type("Msg", (), {"chat_id": "unknown-user", "content": "pong", "media": [], "metadata": {}})()
)
with pytest.raises(RuntimeError, match="No context_token"):
await channel.send(
type("Msg", (), {"chat_id": "unknown-user", "content": "pong", "media": [], "metadata": {}})()
)
channel._send_text.assert_not_awaited()
@pytest.mark.asyncio
async def test_send_does_not_send_when_session_is_paused() -> None:
async def test_send_raises_when_session_is_paused() -> None:
channel, _bus = _make_channel()
channel._client = object()
channel._token = "token"
@@ -341,9 +342,10 @@ async def test_send_does_not_send_when_session_is_paused() -> None:
channel._pause_session(60)
channel._send_text = AsyncMock()
await channel.send(
type("Msg", (), {"chat_id": "wx-user", "content": "pong", "media": [], "metadata": {}})()
)
with pytest.raises(RuntimeError, match="session paused"):
await channel.send(
type("Msg", (), {"chat_id": "wx-user", "content": "pong", "media": [], "metadata": {}})()
)
channel._send_text.assert_not_awaited()
@@ -1213,3 +1215,38 @@ async def test_send_media_network_error_does_not_double_api_calls() -> None:
# _send_media_file called once, _send_text never called
channel._send_media_file.assert_awaited_once()
channel._send_text.assert_not_awaited()
# ---------------------------------------------------------------------------
# Tests for _send_text raising on API errors (previously silently swallowed)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_send_text_raises_on_api_error() -> None:
"""_send_text must raise RuntimeError when the API returns a non-zero errcode,
matching _send_media_file behavior. This ensures ChannelManager can retry."""
channel, _bus = _make_channel()
channel._client = httpx.AsyncClient()
channel._token = "token"
channel._api_post = AsyncMock(
return_value={"errcode": -14, "errmsg": "session expired"}
)
with pytest.raises(RuntimeError, match="WeChat send text error.*-14"):
await channel._send_text("wx-user", "hello", "ctx-expired")
channel._api_post.assert_awaited_once()
@pytest.mark.asyncio
async def test_send_text_succeeds_on_zero_errcode() -> None:
"""_send_text must NOT raise when errcode is 0."""
channel, _bus = _make_channel()
channel._client = httpx.AsyncClient()
channel._token = "token"
channel._api_post = AsyncMock(return_value={"errcode": 0})
await channel._send_text("wx-user", "hello", "ctx-ok")
channel._api_post.assert_awaited_once()