This commit is contained in:
Your Name
2026-07-28 09:00:19 +08:00
parent 8ba13a8ff9
commit 153db97dc7
14 changed files with 793 additions and 75 deletions
@@ -3,8 +3,9 @@ from __future__ import annotations
import os
import sys
import unittest
from contextlib import asynccontextmanager
from pathlib import Path
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, patch
BACKEND_DIR = Path(__file__).resolve().parents[1]
@@ -16,6 +17,8 @@ if str(BACKEND_DIR) not in sys.path:
from rpa_engine.douyin_im.http_client import DouyinImHttpClient
from rpa_engine.douyin_im.session import DouyinImSession
from rpa_engine.douyin_im.service import DouyinImService, _conversation_poll_timing
from rpa_engine.douyin_im import service as service_module
class ConversationPollBandwidthTests(unittest.IsolatedAsyncioTestCase):
@@ -75,6 +78,71 @@ class ConversationPollBandwidthTests(unittest.IsolatedAsyncioTestCase):
["POST", "GET"],
)
async def test_transport_outage_stops_after_one_post_and_get_pair(self):
client = self._make_client()
client._request = AsyncMock(return_value=None)
with self.assertLogs("douyin_im.http", level="WARNING"):
self.assertEqual(await client.get_conversations(), [])
self.assertEqual(client._request.await_count, 2)
self.assertEqual(
[call.args[0] for call in client._request.await_args_list],
["POST", "GET"],
)
def test_websocket_reconciliation_is_slow_and_http_fallback_stays_fast(self):
with patch.dict(
os.environ,
{
"KEFU_WS_RECONCILE_INTERVAL_SECONDS": "120",
"KEFU_HTTP_POLL_INTERVAL_SECONDS": "15",
},
):
ws_interval, ws_stagger = _conversation_poll_timing(123, True)
http_interval, http_stagger = _conversation_poll_timing(123, False)
self.assertEqual(ws_interval, 120)
self.assertEqual(http_interval, 15)
self.assertGreaterEqual(ws_stagger, 0)
self.assertLess(ws_stagger, ws_interval)
self.assertGreaterEqual(http_stagger, 0)
self.assertLess(http_stagger, http_interval)
async def test_service_poll_uses_one_conversation_request_without_unread_probe(self):
class _Controller:
@asynccontextmanager
async def background_slot(self, *_args, **_kwargs):
yield
class _HttpClient:
def __init__(self):
self.get_conversations = AsyncMock(return_value=[])
async def __aenter__(self):
return self
async def __aexit__(self, *_args):
return False
http = _HttpClient()
service = DouyinImService(
session=DouyinImSession(cookies={"sessionid": "test"}, my_uid=10001),
match_reply=AsyncMock(return_value=None),
log_fn=AsyncMock(),
account_id=9,
)
service._index_conversations = AsyncMock()
with (
patch.object(service_module, "get_traffic_controller", return_value=_Controller()),
patch.object(service_module, "DouyinImHttpClient", return_value=http),
):
await service._poll_conversations()
http.get_conversations.assert_awaited_once_with(enrich_profiles=False)
service._index_conversations.assert_awaited_once_with([])
if __name__ == "__main__":
unittest.main()