This commit is contained in:
Your Name
2026-08-26 17:18:09 +08:00
parent 327a0bc42f
commit 4ac6990efe
20 changed files with 1336 additions and 130 deletions
@@ -20,6 +20,7 @@ if str(BACKEND_DIR) not in sys.path:
from rpa_engine.douyin_im import http_client as http_client_module
from rpa_engine.douyin_im.http_client import DouyinImHttpClient
from rpa_engine.douyin_im.session import DouyinImSession
from rpa_engine.egress_channels import EgressChannel
from rpa_engine.playwright_worker import DouyinWorker
@@ -85,6 +86,7 @@ class SendTextMessageEntryTests(unittest.IsolatedAsyncioTestCase):
last_send_meta=queued_meta,
last_error="credential expired",
last_send_needs_refresh=True,
last_send_channel_retryable=False,
last_request_debug="response status=401",
)
queued_context = MagicMock()
@@ -131,6 +133,65 @@ class SendTextMessageEntryTests(unittest.IsolatedAsyncioTestCase):
self.assertTrue(client.last_send_needs_refresh)
self.assertEqual(client.last_request_debug, "response status=401")
async def test_retryable_rejection_switches_channels_serially(self):
client = self._make_client(account_id=89)
client.session.egress_auto_attempts = 2
routes = [
EgressChannel("198.51.100.10", "10.0.0.10", "eth0", True),
EgressChannel("198.51.100.11", "10.0.0.11", "eth0:1", False),
]
first = SimpleNamespace(
send_text_message=AsyncMock(return_value=False),
last_send_meta={},
last_error="decision=KICK",
last_send_needs_refresh=False,
last_send_channel_retryable=True,
last_request_debug="first route",
)
second = SimpleNamespace(
send_text_message=AsyncMock(return_value=True),
last_send_meta={"conv": {"ticket": "ok"}},
last_error="",
last_send_needs_refresh=False,
last_send_channel_retryable=False,
last_request_debug="second route",
)
def context_for(value):
context = MagicMock()
context.__aenter__ = AsyncMock(return_value=value)
context.__aexit__ = AsyncMock(return_value=None)
return context
queued_factory = MagicMock(side_effect=[context_for(first), context_for(second)])
async def execute_submission(account_id, operation, description=""):
self.assertEqual(account_id, 89)
return await operation()
with (
patch(
"rpa_engine.douyin_im.traffic_control.submit_outbound",
AsyncMock(side_effect=execute_submission),
),
patch(
"rpa_engine.douyin_im.http_client.resolve_send_channels",
AsyncMock(return_value=routes),
),
patch.object(http_client_module, "DouyinImHttpClient", queued_factory),
patch.object(http_client_module.system_logger, "record"),
):
sent = await client.send_text_message("0:1:10001:20002", "hello")
self.assertTrue(sent)
self.assertEqual(queued_factory.call_count, 2)
self.assertEqual(queued_factory.call_args_list[0].kwargs["source_ip"], "10.0.0.10")
self.assertEqual(queued_factory.call_args_list[1].kwargs["source_ip"], "10.0.0.11")
first.send_text_message.assert_awaited_once()
second.send_text_message.assert_awaited_once()
self.assertEqual(client.last_request_debug, "second route")
class WorkerLifecycleTests(unittest.IsolatedAsyncioTestCase):
async def test_start_saves_task_and_stop_waits_until_it_is_done(self):