"""积分中心商城链路回归测试(2026-10-13「开通精英宝典 / 购买幸运币」抓包)。 抓包文件不入库,这里把关键帧的字节与字段直接固化在测试里;每条断言的来源 都写在注释里,改动协议时先核对抓包再改测试。 """ import asyncio import base64 import struct from core.huya.frame_decoder import _decode_wup_body, _extract_wup from core.huya.http_client import ( WSConnectParaInfo, _extract_ws_hosts, generate_ws_baseinfo, ) from core.huya.shop_structs import shop_spu_number from core.huya.taf_protocol import TafInputStream, TafOutputStream, TafType from core.huya.wss_client import ( HuyaWssClient, WssCommand, WssMessage, ) from core.huya.wup_protocol import WupResponse UID = 1199668595232 GUID = "0a8995af9218ce6a340261946294a9e7" COOKIE = f"udb_uid={UID}; yyuid={UID}; guid={GUID}" PID = 1199650619883 # 抓包字面值(不要引用实现常量,否则断言会与实现一起漂移) POINTS_UA_LITERAL = "webh5&0.0.1&websocket&&h5_/points.html" TAIL_LITERAL = bytes.fromhex("2c36004c5c6600") BIZ_PARAMS_LITERAL = '{ "jifencenter": 1 }' # 抓包实测:RegisterGroup(live:0) 与 UpdateUserExps(版本 map) 的 body 逐字节一致 HEARTBEAT_BODY = bytes.fromhex("09000106066c6976653a3016002c36004c5c6600") UPDATE_EXPS_BODY = bytes.fromhex("0800010605353836363416063130373236332c36004c5c6600") CONFIG_VERSIONS = {"58664": "107263"} class _FakeWs: """记录发出的帧,并立刻用预设 body 兑现 RPC future(离线跑完整链路)。""" def __init__(self, client: HuyaWssClient, response: bytes = b""): self.client = client self.response = response self.sent: list[bytes] = [] async def send(self, data: bytes): self.sent.append(data) while self.client.rpc_queue: future = self.client.rpc_queue.popleft() if not future.done(): future.set_result(self.response) def last_body(self) -> bytes: return WssMessage.decode(self.sent[-1]).body def last_request(self) -> dict: return _decode_wup_body(self.last_body()) def _points_client(response: bytes = b"") -> HuyaWssClient: client = HuyaWssClient(logger=lambda _message: None) client._points_mode = True client.ws = _FakeWs(client, response) return client def _wup_response(t_rsp: bytes) -> bytes: """构造一个 tRsp 为给定结构的 WUP 响应(含 4 字节长度前缀)。""" buffer = TafOutputStream() buffer.write_head(0, TafType.MAP) buffer.write_int32(0, 1) buffer.write_string(0, "tRsp") buffer.write_bytes(1, t_rsp) output = TafOutputStream() output.write_int16(1, 3) output.write_int8(2, 0) output.write_int32(3, 0) output.write_int32(4, 1) output.write_string(5, "mobileui") output.write_string(6, "getConfig") output.write_bytes(7, buffer.get_bytes()) output.write_int32(8, 0) output.write_map(9, {}) output.write_map(10, {}) body = output.get_bytes() return struct.pack(">I", 4 + len(body)) + body def _config_rsp(versions: dict[str, str]) -> bytes: """mobileui.getConfig 响应:tag0 开关 map、tag1 版本 map(抓包实测)。""" output = TafOutputStream() output.write_struct_begin(0) output.write_map(0, {}) output.write_map(1, versions) output.write_struct_end() return _wup_response(output.get_bytes()) def test_register_group_frame_matches_capture(): async def run(): client = _points_client() await client.send_heartbeat() message = WssMessage.decode(client.ws.sent[-1]) assert message.command == WssCommand.HEARTBEAT_SEND assert message.sequence == 0x1D00000D assert message.body == HEARTBEAT_BODY asyncio.run(run()) def test_shop_ws_launch_carries_real_identity_and_points_ua(): async def run(): client = _points_client(_wup_response(b"")) await client.call_ws_launch_shop(UID, GUID) message = WssMessage.decode(client.ws.sent[-1]) assert message.command == WssCommand.RPC_REQUEST # 抓包:wsLaunch 帧 seq=0x1d000100,iRequestId=-1 assert message.sequence == 0x1D000100 wup = WupResponse() wup.decode(_extract_wup(message.body)) assert wup.iRequestId == -1 decoded = _decode_wup_body(message.body) assert (decoded["servant"], decoded["func"]) == ("launch", "wsLaunch") t_req = decoded["tReq"] # 抓包:lUid=真实 uid、sGuid=真实 guid、UA=h5_/points.html(不再是匿名) assert t_req["tag0"] == UID assert t_req["tag1"] == GUID assert t_req["tag2"] == POINTS_UA_LITERAL assert t_req["tag3"] == "HUYA&ZH&2052" assert t_req["tag4"] == { "tag0": "", "tag1": "", "tag2": "", "tag3": "", "tag4": "", } asyncio.run(run()) def test_get_config_uses_real_user_and_confirms_version_map(): async def run(): client = _points_client(_config_rsp(CONFIG_VERSIONS)) body = await client.call_get_config_shop(uid=UID, guid=GUID, cookie=COOKIE) assert body is not None assert client._config_versions == CONFIG_VERSIONS decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ("mobileui", "getConfig") t_req = decoded["tReq"] # 抓包:getConfig 的 UserId 带真实 uid/guid/页面 UA/完整 Cookie assert t_req["tag0"]["tag0"] == UID assert t_req["tag0"]["tag1"] == GUID assert t_req["tag0"]["tag3"] == POINTS_UA_LITERAL assert GUID in t_req["tag0"]["tag4"] assert t_req["tag2"] == "huya" # UpdateUserExps 必须原样回传版本 map(抓包逐字节一致) await client.send_confirm(client._config_versions) assert client.ws.last_body() == UPDATE_EXPS_BODY asyncio.run(run()) def test_create_order_v5_matches_points_capture(): async def run(): client = _points_client() await client.create_order( uid=UID, guid=GUID, cookie=COOKIE, pid=PID, spu_id="hy-8328015", sku_id=7756811, item_count=1, ) message = WssMessage.decode(client.ws.sent[-1]) # 抓包:下单帧用 0x1d000108(其余业务帧 0x1d000107) assert message.sequence == 0x1D000108 decoded = _decode_wup_body(message.body) assert (decoded["servant"], decoded["func"]) == ( "shopMiddleUI", "createOrderV5", ) t_req = decoded["tReq"] assert t_req["tag1"]["tag4"] == 7 # shopAppInfo.scene=7(appInfo 用 7) assert t_req["tag3"] == PID assert t_req["tag4"] == 7756811 assert t_req["tag5"] == 1 assert t_req["tag7"] == "hy-8328015" assert t_req["tag8"] == "0" assert t_req["tag10"] == 4 # src assert t_req["tag12"] == 6 # orderType assert t_req["tag13"]["tag4"] == BIZ_PARAMS_LITERAL assert t_req["tag16"] == "yellowcarlist" assert t_req["tag17"] == {"user_click_seq": "[]"} assert t_req["tag18"] == 4 # orderScene assert t_req["tag21"]["tag2"] == 1 assert t_req["tag30"] == 5 assert t_req["tag31"] == 507 asyncio.run(run()) def test_pay_order_submit_v5_callback_points_page(): async def run(): client = _points_client() await client.pay_order_submit( uid=UID, guid=GUID, cookie=COOKIE, order_id=11339649, pay_type=1, pid=PID, ) message = WssMessage.decode(client.ws.sent[-1]) assert message.sequence == 0x1D000108 decoded = _decode_wup_body(message.body) assert (decoded["servant"], decoded["func"]) == ( "shopMiddleUI", "payOrderSubmitV5", ) t_req = decoded["tReq"] assert t_req["tag2"] == 11339649 assert t_req["tag3"] == "Zfb" assert t_req["tag4"] == "QrCode" # 抓包:回调指向 m-shop.huya.com/points.html,不是 yaoguo 域名 assert t_req["tag5"].startswith( "https://m-shop.huya.com/points.html#/consumer/paycallback?orderId=11339649" ) assert t_req["tag6"] == {"user_click_seq": "[]"} assert t_req["tag7"] == "null" assert t_req["tag8"] == "" asyncio.run(run()) def test_new_points_rpcs_match_capture(): async def run(): client = _points_client() await client.get_supplier_info(uid=UID, guid=GUID, cookie=COOKIE) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "shopMiddleUI", "getSupplierInfoV5", ) assert decoded["tReq"]["tag2"] == 87401 await client.accept_hy_protocol(uid=UID, guid=GUID, cookie=COOKIE) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "shopMiddleUI", "acceptHyProtocolV5", ) assert decoded["tReq"]["tag2"] == 1 await client.calc_order_promotion( uid=UID, guid=GUID, cookie=COOKIE, pid=PID, spu_id="hy-8328015", sku_id=7756811, ) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "shopMiddleUI", "calcOrderPromotion", ) t_req = decoded["tReq"] assert t_req["tag2"] == 8328015 # spuId 去前缀后的数字 assert t_req["tag3"] == 7756811 assert t_req["tag4"] == 1 assert t_req["tag5"] == PID assert (t_req["tag7"], t_req["tag8"], t_req["tag9"]) == (1, 1, 1) assert t_req["tag14"] == 8328015 assert t_req["tag15"] == 9815 await client.play_mall_query_allocation(uid=UID, guid=GUID, cookie=COOKIE) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "PlayMallUI", "queryAllocation", ) t_req = decoded["tReq"] assert t_req["tag1"]["tag1"] == 1001 assert t_req["tag1"]["tag5"] == 7 assert t_req["tag1"]["tag6"] == "yellowcarlist" assert t_req["tag2"] == "heping_baodian_credit_center_page" await client.play_mall_get_my_promotion( uid=UID, guid=GUID, cookie=COOKIE, spu_id="hy-8328015", sku_id=7756811, price=3000, ) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "PlayMallUI", "getMyPromotion", ) goods = decoded["tReq"]["tag6"] assert goods["tag0"] == 87401 assert goods["tag1"] == 8328015 assert goods["tag2"] == 7756811 assert goods["tag3"] == 3000 assert goods["tag6"] == 9815 await client.get_user_profile(uid=UID, guid=GUID, cookie=COOKIE) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "huyauserui", "getUserProfile", ) assert decoded["tReq"]["tag1"] == UID await client.get_follow_wechat_status(uid=UID, guid=GUID, cookie=COOKIE) decoded = client.ws.last_request() assert (decoded["servant"], decoded["func"]) == ( "wupui", "getUserFollowWeChatOfficialStatus", ) assert decoded["tReq"]["tag1"] == 1 asyncio.run(run()) def test_confirm_frame_ends_with_capture_tail(): async def run(): client = _points_client() await client.send_confirm({}) assert client.ws.last_body().endswith(TAIL_LITERAL) asyncio.run(run()) def test_ws_baseinfo_matches_capture_shape(): raw = base64.b64decode(generate_ws_baseinfo(UID, GUID, POINTS_UA_LITERAL)) info = WSConnectParaInfo() info.read_from(TafInputStream(raw)) # 抓包:握手 baseinfo 只有 uid/guid/UA/appSrc/exp 有值,cookie 与 traceId 为空 assert info.lUid == UID assert info.sGuid == GUID assert info.sUA == POINTS_UA_LITERAL assert info.sAppSrc == "HUYA&ZH&2052" assert info.sCookie == "" assert info.sTraceId == "" assert info.sExp == "" with_exp = base64.b64decode( generate_ws_baseinfo(UID, GUID, POINTS_UA_LITERAL, exp="58664.107263") ) parsed = WSConnectParaInfo() parsed.read_from(TafInputStream(with_exp)) assert parsed.sExp == "58664.107263" def test_query_http_dns_hosts_extraction(): payload = ( b"\x1a\x09\x00\x04" b"\x06\x17d35bf201-ws.va.huya.com" b"\x06\x17d35bf373-ws.va.huya.com" b"\x06\x17d35bf35c-ws.va.huya.com" b"\x06\x17dcca159d-ws.va.huya.com" ) assert _extract_ws_hosts(payload) == [ "d35bf201-ws.va.huya.com", "d35bf373-ws.va.huya.com", "d35bf35c-ws.va.huya.com", "dcca159d-ws.va.huya.com", ] assert _extract_ws_hosts(b"") == [] def test_shop_spu_number_strips_prefix(): assert shop_spu_number("hy-8328015") == 8328015 assert shop_spu_number("8327356") == 8327356 assert shop_spu_number("") == 0