This commit is contained in:
Your Name
2026-09-01 15:31:05 +08:00
parent 1f3addcf79
commit 2fc864cc00
20 changed files with 1381 additions and 45 deletions
@@ -205,7 +205,7 @@ 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):
async def test_retryable_network_failure_switches_channels_serially(self):
client = self._make_client(account_id=89)
client.session.egress_auto_attempts = 2
routes = [
@@ -216,7 +216,7 @@ class SendTextMessageEntryTests(unittest.IsolatedAsyncioTestCase):
first = SimpleNamespace(
send_text_message=AsyncMock(return_value=False),
last_send_meta={},
last_error="decision=KICK",
last_error="connect timeout",
last_send_needs_refresh=False,
last_send_channel_retryable=True,
last_request_debug="first route",
@@ -264,8 +264,82 @@ class SendTextMessageEntryTests(unittest.IsolatedAsyncioTestCase):
second.send_text_message.assert_awaited_once()
self.assertEqual(client.last_request_debug, "second route")
async def test_kick_never_switches_public_channels(self):
client = self._make_client(account_id=90)
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", "eth1", False),
]
kicked = SimpleNamespace(
send_text_message=AsyncMock(return_value=False),
last_send_meta={},
last_error="decision=KICK",
last_send_needs_refresh=False,
last_send_channel_retryable=False,
last_request_debug="terminal kick",
)
context = MagicMock()
context.__aenter__ = AsyncMock(return_value=kicked)
context.__aexit__ = AsyncMock(return_value=None)
queued_factory = MagicMock(return_value=context)
async def execute_submission(account_id, operation, description=""):
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),
):
sent = await client.send_text_message("0:1:10001:20002", "hello")
self.assertFalse(sent)
queued_factory.assert_called_once()
kicked.send_text_message.assert_awaited_once()
class WorkerLifecycleTests(unittest.IsolatedAsyncioTestCase):
async def test_browser_launch_uses_selected_source_proxy(self):
launch = AsyncMock(return_value="browser")
pw = SimpleNamespace(chromium=SimpleNamespace(launch=launch))
source_proxy = AsyncMock(return_value={"server": "http://127.0.0.1:43210"})
with (
patch.object(
playwright_worker_module,
"ensure_browser_display",
AsyncMock(),
),
patch.object(
playwright_worker_module,
"playwright_proxy_for_source",
source_proxy,
),
patch.object(playwright_worker_module, "playwright_proxy") as global_proxy,
):
browser = await playwright_worker_module._launch_chromium(
pw,
["--no-sandbox"],
headless=True,
source_ip="10.0.0.6",
)
self.assertEqual(browser, "browser")
source_proxy.assert_awaited_once_with("10.0.0.6")
global_proxy.assert_not_called()
launch.assert_awaited_once_with(
headless=True,
args=["--no-sandbox"],
proxy={"server": "http://127.0.0.1:43210"},
)
async def test_virtual_display_starts_before_playwright_driver(self):
ensure_display = AsyncMock()