mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-20 16:42:25 +00:00
fix(weixin): check both ret and errcode on send to avoid silent drops
The iLink API signals failures through either `ret` or `errcode`. `_poll_once` already checked both, but `_send_text` and `_send_media_file` only checked `errcode`. When the API returned `ret != 0` with `errcode == 0`, the send appeared successful but the message was never delivered, causing the "still losing messages" issue. - Add `_check_response_error` helper that validates both fields - Use it in `_send_text` and `_send_media_file` - Add debug log after successful text send for observability - Add test for nonzero ret with zero errcode Refs: previous inbound fix (suppress -> explicit try/except)
This commit is contained in:
parent
2a318d6991
commit
e9f4a868a8
@ -526,6 +526,22 @@ class WeixinChannel(BaseChannel):
|
|||||||
f"WeChat session paused, {remaining_min} min remaining (errcode {ERRCODE_SESSION_EXPIRED})"
|
f"WeChat session paused, {remaining_min} min remaining (errcode {ERRCODE_SESSION_EXPIRED})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_response_error(data: dict, operation: str) -> None:
|
||||||
|
"""Check both ``ret`` and ``errcode`` like the reference TS code.
|
||||||
|
|
||||||
|
The iLink API may signal failure through either field (or both).
|
||||||
|
``_poll_once`` already checks both; outbound send helpers must do
|
||||||
|
the same to avoid silent drops.
|
||||||
|
"""
|
||||||
|
ret = data.get("ret", 0)
|
||||||
|
errcode = data.get("errcode", 0)
|
||||||
|
is_error = (ret is not None and ret != 0) or (errcode is not None and errcode != 0)
|
||||||
|
if is_error:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"WeChat {operation} error (ret={ret}, errcode={errcode}): {data.get('errmsg', '')}"
|
||||||
|
)
|
||||||
|
|
||||||
async def _poll_once(self) -> None:
|
async def _poll_once(self) -> None:
|
||||||
remaining = self._session_pause_remaining_s()
|
remaining = self._session_pause_remaining_s()
|
||||||
if remaining > 0:
|
if remaining > 0:
|
||||||
@ -1123,11 +1139,8 @@ class WeixinChannel(BaseChannel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = await self._api_post("ilink/bot/sendmessage", body)
|
data = await self._api_post("ilink/bot/sendmessage", body)
|
||||||
errcode = data.get("errcode", 0)
|
self._check_response_error(data, "send text")
|
||||||
if errcode and errcode != 0:
|
self.logger.debug("WeChat text sent to {} (client_id={})", to_user_id, client_id)
|
||||||
raise RuntimeError(
|
|
||||||
f"WeChat send text error (code {errcode}): {data.get('errmsg', '')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _send_media_file(
|
async def _send_media_file(
|
||||||
self,
|
self,
|
||||||
@ -1273,11 +1286,7 @@ class WeixinChannel(BaseChannel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = await self._api_post("ilink/bot/sendmessage", body)
|
data = await self._api_post("ilink/bot/sendmessage", body)
|
||||||
errcode = data.get("errcode", 0)
|
self._check_response_error(data, "send media")
|
||||||
if errcode and errcode != 0:
|
|
||||||
raise RuntimeError(
|
|
||||||
f"WeChat send media error (code {errcode}): {data.get('errmsg', '')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -1252,6 +1252,26 @@ async def test_send_text_succeeds_on_zero_errcode() -> None:
|
|||||||
channel._api_post.assert_awaited_once()
|
channel._api_post.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_text_raises_on_nonzero_ret_even_when_errcode_zero() -> None:
|
||||||
|
"""_send_text must raise when the API returns ret != 0, even if errcode is 0.
|
||||||
|
|
||||||
|
The iLink API signals failure through either field. Checking only errcode
|
||||||
|
caused silent message drops (responses generated but never delivered).
|
||||||
|
"""
|
||||||
|
channel, _bus = _make_channel()
|
||||||
|
channel._client = object()
|
||||||
|
channel._token = "token"
|
||||||
|
channel._api_post = AsyncMock(
|
||||||
|
return_value={"ret": -100, "errcode": 0, "errmsg": "internal error"}
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="WeChat send text error.*ret=-100.*errcode=0"):
|
||||||
|
await channel._send_text("wx-user", "hello", "ctx-ok")
|
||||||
|
|
||||||
|
channel._api_post.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tests for _poll_once not silently dropping messages on processing errors
|
# Tests for _poll_once not silently dropping messages on processing errors
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user