"""虎牙积分兑换新链路:直兑、购买积分包后再兑、前置条件。""" from __future__ import annotations from types import SimpleNamespace from web.backend.models import HuyaAccount from web.backend.services import huya_points_exchange as exchange from web.backend.services import huya_runner_goods from web.backend.services.huya_runner import HuyaBatchRunner from web.backend.services.huya_shop_flow import ShopOrderOutcome class _Response: def __init__(self, code=200, message="", payload=None, detail=None): self.code = code self.message = message self.payload = payload or {} self._detail = detail or {} def value(self, *path): node = self.payload for key in path: if not isinstance(node, dict): return None node = node.get(key) return node def to_dict(self): return dict(self._detail or self.payload) class _Client: def __init__(self, states, *, goods_code=200): self.states = list(states) self.goods_code = goods_code self.create_v6_calls = [] def get_redeem_need_goods(self, **_kwargs): return self.states.pop(0) def get_goods_info(self, **kwargs): return _Response( code=self.goods_code, message="" if self.goods_code == 200 else "商品详情失败", detail={ "sku_id": 7747527, "name": "幸运币礼包(小)", "price": 20, "goods_type": 5, "virtual_type": 507, "spu_id": kwargs["spu_id"], }, ) def create_order_v6(self, **kwargs): self.create_v6_calls.append(kwargs) return SimpleNamespace( code=200, message="", orderId=12104642, to_dict=lambda: { "code": 200, "order_id": 12104642, "points": 20, }, ) def _state(*, full: bool, precondition: bool = True, plans=None): return _Response( payload={ "tag2": 20, "tag3": 20, "tag4": 0 if full else 20, "tag5": 1 if precondition else 0, "tag6": 1 if full else 0, "tag7": plans or [], } ) def _mock_live_goods( monkeypatch, *, product_id: str = "hy-8315982", name: str = "幸运币礼包(小)", ): monkeypatch.setattr( huya_runner_goods, "fetch_points_center_catalog", lambda *_args, **_kwargs: SimpleNamespace( goods=[ { "product_id": product_id, "name": name, "price": 20, "remain_text": "100", } ], message="已拉取 1 个礼包", ), ) def test_points_enough_uses_create_order_v6_without_payment(monkeypatch): client = _Client([_state(full=True)]) purchase_called = False def fail_purchase(*_args, **_kwargs): nonlocal purchase_called purchase_called = True raise AssertionError("积分足够时不应购买积分包") monkeypatch.setattr(exchange, "run_open_flow", fail_purchase) result = exchange.prepare_exchange( client, uid=1199669523011, cookie="yyuid=1199669523011", pid=1199650619883, spu_id="hy-8315982", ) assert result.status == exchange.EXCHANGE_STATUS_EXCHANGED assert result.message == "兑换成功: 幸运币礼包(小)" assert result.order["order_id"] == 12104642 assert purchase_called is False assert client.create_v6_calls[0]["items"] == [ { "spu_id": "hy-8315982", "sku_id": 7747527, "item_count": 1, "goods_type": 5, "virtual_type": 507, } ] def test_points_shortage_returns_points_package_payment(monkeypatch): client = _Client( [ _state( full=False, plans=[ { "tag0": 8327356, "tag1": 7759407, "tag2": "精英币", "tag5": 1, "tag6": 10, "tag7": 20, "tag9": 2, "tag21": 20, } ], ) ] ) calls = [] def fake_purchase(client_arg, **kwargs): calls.append((client_arg, kwargs)) return ShopOrderOutcome( ok=True, message="已生成支付二维码", order_id=12095620, pay_url="https://openapi.alipay.com/gateway.do?probe=1", amount=200, product_name="精英币", count=20, ) monkeypatch.setattr(exchange, "run_open_flow", fake_purchase) result = exchange.prepare_exchange( client, uid=1199669523011, cookie="yyuid=1199669523011", pid=1199650619883, spu_id="hy-8315982", pay_channel="Zfb", ) assert result.status == exchange.EXCHANGE_STATUS_PAYMENT_REQUIRED assert result.plan is not None assert result.plan["spu_id"] == "hy-8327356" assert result.plan["sku_id"] == 7759407 assert result.plan["count"] == 20 assert result.purchase is not None assert result.purchase.order_id == 12095620 assert calls[0][1]["spu_id"] == "hy-8327356" assert calls[0][1]["count"] == 20 assert client.create_v6_calls == [] def test_complete_after_payment_waits_for_points_then_uses_v6(): client = _Client([_state(full=False), _state(full=True)]) result = exchange.complete_exchange_after_payment( client, uid=1199669523011, cookie="yyuid=1199669523011", pid=1199650619883, spu_id="hy-8315982", attempts=2, interval=0, sleep=lambda _seconds: None, ) assert result.status == exchange.EXCHANGE_STATUS_EXCHANGED assert result.state is not None assert result.state.user_score == 20 assert result.order["order_id"] == 12104642 assert len(client.create_v6_calls) == 1 def test_precondition_failure_does_not_create_order(): client = _Client([_state(full=True, precondition=False)]) result = exchange.prepare_exchange( client, uid=1199669523011, cookie="yyuid=1199669523011", pid=1199650619883, spu_id="hy-8315982", ) assert result.status == exchange.EXCHANGE_STATUS_FAILED assert "前置条件" in result.message assert client.create_v6_calls == [] def test_runner_uses_points_exchange_service_for_direct_redeem(monkeypatch): client = _Client([_state(full=True)]) _mock_live_goods(monkeypatch) runner = HuyaBatchRunner( db=None, batch_id="exchange-direct", task_type="exchange_goods", payload={"product_id": "hy-8315982"}, ) marks = [] monkeypatch.setattr(runner, "_http_client", lambda **_kwargs: client) monkeypatch.setattr( runner, "_mark_task", lambda _db, _task, status, message, result=None: marks.append( (status, message, result) ), ) account = HuyaAccount(uid="1199669523011", cookie="yyuid=1199669523011") runner._execute_exchange_goods( None, SimpleNamespace(id=1), account, {"uid": 1199669523011, "cookie": "yyuid=1199669523011"}, {"room_pid": "1199650619883", "pay_channel": "Zfb"}, ) assert marks[-1][0] == "success" assert marks[-1][2]["exchange_mode"] == "points_only" assert marks[-1][2]["exchange_order"]["order_id"] == 12104642 assert account.status == "goods_exchanged" assert len(client.create_v6_calls) == 1 def test_runner_waits_points_payment_then_redeems(monkeypatch): client = _Client( [ _state( full=False, plans=[ { "tag0": 8327356, "tag1": 7759407, "tag2": "精英币", "tag5": 1, "tag6": 10, "tag7": 20, "tag9": 2, } ], ), _state(full=True), ] ) _mock_live_goods(monkeypatch) runner = HuyaBatchRunner( db=None, batch_id="exchange-pay", task_type="exchange_goods", payload={"product_id": "hy-8315982"}, ) marks = [] progress = [] monkeypatch.setattr(runner, "_http_client", lambda **_kwargs: client) monkeypatch.setattr( runner, "_mark_task", lambda _db, _task, status, message, result=None: marks.append( (status, message, result) ), ) monkeypatch.setattr( runner, "_update_task_progress", lambda _db, _task, status, message, result=None: progress.append( (status, message, result) ), ) monkeypatch.setattr( runner, "_wait_recharge_payment", lambda **_kwargs: ("paid", {"order_id": 12095620, "pay_time": 1}), ) monkeypatch.setattr( exchange, "run_open_flow", lambda *_args, **_kwargs: ShopOrderOutcome( ok=True, message="已生成支付二维码", order_id=12095620, pay_url="https://openapi.alipay.com/gateway.do?probe=1", amount=200, product_name="精英币", count=20, ), ) account = HuyaAccount(uid="1199669523011", cookie="yyuid=1199669523011") runner._execute_exchange_goods( None, SimpleNamespace(id=2), account, {"uid": 1199669523011, "cookie": "yyuid=1199669523011"}, { "room_pid": "1199650619883", "pay_channel": "Zfb", "sid": "2203", }, ) assert progress[0][0] == "running" assert progress[0][2]["exchange_mode"] == "buy_points_then_exchange" assert progress[0][2]["points_purchase"]["order_id"] == 12095620 assert marks[-1][0] == "success" assert marks[-1][2]["exchange_order"]["order_id"] == 12104642 assert account.points == 20 def test_runner_refresh_goods_uses_points_center_query(monkeypatch): runner = HuyaBatchRunner( db=None, batch_id="refresh-points-goods", task_type="refresh_goods", ) marks = [] added = [] calls = [] class _Query: def filter(self, *_args, **_kwargs): return self def delete(self, **_kwargs): return 0 class _DB: def query(self, *_args, **_kwargs): return _Query() def add(self, item): added.append(item) monkeypatch.setattr( huya_runner_goods, "fetch_points_center_catalog", lambda uid, cookie, **kwargs: ( calls.append((uid, cookie, kwargs)) or SimpleNamespace( goods=[ { "product_id": "hy-8315982", "name": "幸运币礼包(小)", "price": 20, "remain_text": "11197", "stock": 11197, } ], message="已拉取 1 个礼包", ) ), ) monkeypatch.setattr( runner, "_mark_task", lambda _db, _task, status, message, result=None: marks.append( (status, message, result) ), ) account = SimpleNamespace( id=8, uid="1199669523011", cookie="yyuid=1199669523011", points=0, status="", updated_at=None, ) runner._execute_refresh_goods( _DB(), SimpleNamespace(id=9, batch_id="refresh-points-goods", result=None), account, {"uid": 1199669523011, "cookie": "yyuid=1199669523011"}, { "sid": "2203", "points_page_code": "heping_baodian_credit_center_page", "points_source_id": "yellowcarlist", "points_scene": "7", }, ) assert calls[0][2]["page_code"] == "heping_baodian_credit_center_page" assert calls[0][2]["source_id"] == "yellowcarlist" assert len(added) == 1 assert added[0].product_id == "hy-8315982" assert marks[-1][2]["source"] == "PlayMallUI.queryAllocation" def test_exchange_record_query_discovers_current_sid(monkeypatch): record_calls = [] class _PointsClient: def play_mall_query_allocation(self, **_kwargs): return SimpleNamespace( code=200, payload={ "tag2": [ { "tag2_2": { "tag0": { "tag7": 2277, } } } ] }, ) def get_user_prize_records(self, **kwargs): record_calls.append(kwargs) return SimpleNamespace( status=200, msg="请求成功", to_dict=lambda: { "status": 200, "msg": "请求成功", "records": [ { "record_id": 9922423, "prize_name": "幸运币礼包(小)", "score": 20, "exchange_time": 1794564026, } ], }, ) runner = HuyaBatchRunner( db=None, batch_id="exchange-records", task_type="query_exchange_records", ) marks = [] monkeypatch.setattr(runner, "_http_client", _PointsClient) monkeypatch.setattr( runner, "_mark_task", lambda _db, _task, status, message, result=None: marks.append( (status, message, result) ), ) account = SimpleNamespace(status="", updated_at=None) runner._execute_query_exchange_records( None, SimpleNamespace(id=3), account, {"uid": 1199669523011, "cookie": "yyuid=1199669523011"}, { "sid": "2203", "points_page_code": "heping_baodian_credit_center_page", "points_source_id": "yellowcarlist", "points_scene": "7", }, ) assert record_calls[0]["sid"] == 2277 assert marks[-1][0] == "success" assert marks[-1][2]["sid"] == 2277 assert marks[-1][2]["sid_source"] == "queryAllocation" assert marks[-1][2]["account_snapshot"]["uid"] == "1199669523011"