From 9f743e50a62abfd60861b2a9bdcb6719e82e5e3b Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 30 Aug 2026 11:25:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(douyu):=20=E8=A1=A5=E9=BD=90=E6=8B=86?= =?UTF-8?q?=E5=88=86=E5=90=8E=E6=94=AF=E4=BB=98=E8=BD=AE=E8=AF=A2=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E5=AF=BC=E5=85=A5=20+=20=E4=BB=A3=E7=90=86=E4=B8=89?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E6=96=AD=E8=A8=80=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - douyu_runner_manual/gold: 导入 core 的 DOUYU_PAYMENT_POLL_* 常量 (之前仅在 douyu_runner.py 单文件内可见, 拆分后到账轮询会 NameError) - 新增 tests/test_douyu_runner_proxy.py: 静态/API/关闭三模式 + 降级直连 + activity_client session 级 proxies, 兑现 8fe9fdf 提交说明中的行为断言 --- tests/test_douyu_runner_proxy.py | 102 ++++++++++++++++++++ web/backend/services/douyu_runner_gold.py | 1 + web/backend/services/douyu_runner_manual.py | 1 + 3 files changed, 104 insertions(+) create mode 100644 tests/test_douyu_runner_proxy.py diff --git a/tests/test_douyu_runner_proxy.py b/tests/test_douyu_runner_proxy.py new file mode 100644 index 0000000..22c64c5 --- /dev/null +++ b/tests/test_douyu_runner_proxy.py @@ -0,0 +1,102 @@ +"""斗鱼任务代理接入三模式行为测试(静态 / API / 关闭)。""" + +import os +import unittest +from unittest.mock import Mock + +os.environ.setdefault("DATABASE_URL", "sqlite://") +os.environ.setdefault("APP_ENCRYPTION_KEY", "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=") + +from core.douyu.activity_client import DouyuActivityClient +from web.backend.services.douyu_runner import DouyuBatchRunner +from web.backend.services.douyu_runner_core import DouyuBatchRunnerCore +from web.backend.models import ProxyConfig + + +def _cfg(**overrides) -> ProxyConfig: + values = { + "enabled": False, + "api_url": "", + "http": "", + "https": "", + "whitelist_enabled": False, + "whitelist_platform": "xiequ", + "whitelist_credentials": None, + "whitelist_uid": "", + "whitelist_ukey": "", + } + values.update(overrides) + cfg = ProxyConfig() + for key, value in values.items(): + setattr(cfg, key, value) + return cfg + + +def _runner(db_cfg: ProxyConfig): + db = Mock() + db.query.return_value.first.return_value = db_cfg + runner = DouyuBatchRunner.__new__(DouyuBatchRunner) + DouyuBatchRunnerCore.__init__(runner, db, "batch", "query_points") + return runner + + +class DouyuProxyModeTests(unittest.TestCase): + def test_disabled_mode_goes_direct(self): + runner = _runner(_cfg(enabled=False)) + self.assertIsNone(runner._proxies_for_task()) + self.assertIsNone(runner._proxy_fetcher) + client = runner._client("acf_uid=100") + self.assertEqual(client.session.proxies, {}) + # 未开启代理时 execute 客户端不带 proxies 参数 + self.assertIsInstance(client, DouyuActivityClient) + + def test_static_mode_uses_configured_proxies(self): + runner = _runner(_cfg(enabled=True, http="http://10.0.0.1:8080")) + proxies = runner._proxies_for_task() + self.assertEqual(proxies, {"http": "http://10.0.0.1:8080", "https": "http://10.0.0.1:8080"}) + client = runner._client("acf_uid=100") + self.assertEqual(client.session.proxies["https"], "http://10.0.0.1:8080") + # 每次任务都取同一静态代理 + self.assertEqual(runner._proxies_for_task(), proxies) + + def test_api_mode_fetches_new_proxy_per_task(self): + cfg = _cfg(enabled=True, api_url="https://proxy-api.example/fetch") + runner = _runner(cfg) + self.assertIsNotNone(runner._proxy_fetcher) + fake = Mock() + fake.fetch_new_proxy.return_value = "http://10.0.0.2:3128" + runner._proxy_fetcher = fake + + proxies = runner._proxies_for_task() + self.assertEqual(proxies, {"http": "http://10.0.0.2:3128", "https": "http://10.0.0.2:3128"}) + client = runner._client("acf_uid=100") + self.assertEqual(client.session.proxies["http"], "http://10.0.0.2:3128") + + # 每任务取新代理:_client 也会再 fetch 一次(客户端构造即取代理) + fake.fetch_new_proxy.return_value = "http://10.0.0.3:3128" + self.assertEqual(runner._proxies_for_task()["http"], "http://10.0.0.3:3128") + self.assertEqual(fake.fetch_new_proxy.call_count, 3) + + def test_api_mode_failure_falls_back_to_direct(self): + runner = _runner(_cfg(enabled=True, api_url="https://proxy-api.example/fetch")) + runner._proxy_fetcher = Mock() + runner._proxy_fetcher.fetch_new_proxy.return_value = None + runner._push_log = Mock() + + self.assertIsNone(runner._proxies_for_task()) + client = runner._client("acf_uid=100") + self.assertEqual(client.session.proxies, {}) + runner._push_log.assert_called() + + def test_activity_client_accepts_proxies_at_session_level(self): + client = DouyuActivityClient( + "acf_uid=100", + proxies={"http": "http://10.0.0.9:7890", "https": "http://10.0.0.9:7890"}, + ) + self.assertEqual(client.session.proxies["http"], "http://10.0.0.9:7890") + # trust_env=False 维持不变:不受环境变量代理影响 + self.assertFalse(client.session.trust_env) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/web/backend/services/douyu_runner_gold.py b/web/backend/services/douyu_runner_gold.py index 28f527a..f232d4e 100644 --- a/web/backend/services/douyu_runner_gold.py +++ b/web/backend/services/douyu_runner_gold.py @@ -10,6 +10,7 @@ from sqlalchemy.orm import Session from core.douyu import DouyuActivityClient, FishFinRechargeClient, FishFinRechargeConfig, FishFinRechargeError from ..models import Account, DouyuTask from .douyu_service import update_account_profile_from_cookie +from .douyu_runner_core import DOUYU_PAYMENT_POLL_INTERVAL, DOUYU_PAYMENT_POLL_SECONDS class GoldMixin: """鱼翅充值域:扫码充值、供应商直充与到账轮询。""" diff --git a/web/backend/services/douyu_runner_manual.py b/web/backend/services/douyu_runner_manual.py index cba66a7..24eab64 100644 --- a/web/backend/services/douyu_runner_manual.py +++ b/web/backend/services/douyu_runner_manual.py @@ -8,6 +8,7 @@ from sqlalchemy.orm import Session from core.douyu import DouyuActivityClient, DouyuActivityError from ..models import Account, DouyuTask from .douyu_service import account_uid, update_account_profile_from_cookie +from .douyu_runner_core import DOUYU_PAYMENT_POLL_INTERVAL, DOUYU_PAYMENT_POLL_SECONDS class ManualMixin: """手册域:精英/电竞手册开通支付、积分查询与到账轮询。"""