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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user