Files
live-hub-py/tests/test_huya_dfp_register.py
T

191 lines
6.6 KiB
Python

"""虎牙新设备注册链 (core/huya/dfp_register) 测试。
覆盖:
* 随机 dfpReport body/cw 结构 (4226B / 4146B / 固定10B尾);
* dfpReport 响应解析 (t1/t2/t5) 及缺字段失败;
* register_device 全流程: 三步 POST 顺序与 content-type、fingerprint 注入、
注册结果三元组返回;
* 失败语义: 模板缺失/HTTP 异常 -> DfpRegistrationError (不静默、不发登录帧)。
"""
import hashlib
from unittest.mock import patch
import pytest
import requests
from core.huya.dfp_register import (
CW_LEN,
SELECT_OPERATOR_SDID,
DfpRegistrationError,
_build_dfp_json_plain,
_build_get_dfp_config_request,
_build_random_dfp_body,
_build_select_operator_request,
_parse_response,
register_device,
)
from core.huya.wup_protocol import WupResponse
TAIL = bytes.fromhex("3600400c0b8c980ca80c")
def _fake_resp() -> bytes:
"""合成 dfpReport 响应: t1(32hex) + t2(180B base64 action) + t5(40hex)。"""
return b"\x16\x20" + b"a" * 32 + b"\x26\xb4" + b"A" * 180 + b"\x56\x28" + b"b" * 40
_GOLDEN_FP = b"02df398797432eadefcc12767119ad5e80999389"
def _dummy_chain() -> dict:
# selectOperator 请求体包含金样本 fingerprint, 供替换注入测试
return {
"getDfpConfig": (b"cfg-req", b"cfg-resp"),
"selectOperator": (b"sel-hdr" + _GOLDEN_FP + b"sel-tail", b"sel-resp"),
"dfpReport": (b"dfp-req", b"dfp-resp"),
}
class TestRandomDfpBody:
def test_body_shape(self):
body = _build_random_dfp_body()
assert len(body) == 4226
cw = body[-CW_LEN:]
assert len(cw) == CW_LEN # 4146
assert cw.endswith(TAIL)
def test_body_is_randomized(self):
b1 = _build_random_dfp_body()
b2 = _build_random_dfp_body()
assert b1 != b2, "每次注册应生成不同的随机 cw"
def test_body_uses_device_profile_fields(self):
profile = {
"app_version": "13.4.22",
"vendor": "vivo",
"model": "V2370A",
"screen": "V2370A,34,13",
"width": "1080",
"height": "2412",
"device_id": "d" * 64,
"guid32": "g" * 64,
}
with (
patch(
"core.huya.dfp_register._random_triple",
return_value=("h" * 64, "r" * 64, "k" * 64),
),
):
plaintext = _build_dfp_json_plain(profile)
assert b'"channel":"vivo"' in plaintext
assert b'"deviceName":"V2370A"' in plaintext
assert b'"deviceId":"' + b"d" * 64 in plaintext
assert b'"systemVer":"V2370A,34,13"' in plaintext
with (
patch(
"core.huya.dfp_register._random_triple",
return_value=("h" * 64, "r" * 64, "k" * 64),
),
patch("core.huya.dfp_register.os.urandom", side_effect=lambda n: b"\0" * n),
):
body = _build_random_dfp_body(profile)
assert hashlib.sha256(plaintext).digest() in body[-CW_LEN + 2 :]
class TestParseResponse:
def test_parse_ok(self):
t1, t2, t5 = _parse_response(_fake_resp())
assert t1 == "a" * 32
assert t2 == "A" * 180
assert t5 == "b" * 40
def test_parse_missing_fields_raises(self):
with pytest.raises(DfpRegistrationError):
_parse_response(b"\x16\x20" + b"a" * 32) # 缺 t2/t5
with pytest.raises(DfpRegistrationError):
_parse_response(b"\x26\xb4" + b"A" * 180) # 缺 t1/t5
with pytest.raises(DfpRegistrationError):
_parse_response(b"\x56\x28" + b"b" * 40) # 缺 t1/t2
class TestRegisterDevice:
def test_get_dfp_config_request_is_generated(self):
req = _build_get_dfp_config_request()
assert len(req) == 67
wup = WupResponse()
wup.decode(req)
assert wup.sServantName == "huyaudbwebui"
assert wup.sFuncName == "getDfpConfig"
assert wup.readStruct("tReq") == bytes.fromhex("0a0604353030380b")
def test_select_operator_request_is_generated(self):
fp = "0" * 40
req = _build_select_operator_request(fp)
assert len(req) == 419
assert fp.encode() in req
assert SELECT_OPERATOR_SDID.encode() in req
wup = WupResponse()
wup.decode(req)
assert wup.sServantName == "huyaudbwebui"
assert wup.sFuncName == "selectOperator"
assert "_wup_data" in wup.newdata
def test_flow_three_steps_and_returns_triple(self):
calls = []
def spy_post(
body, content_type="application/octet-stream", timeout=20, proxies=None
):
calls.append(content_type)
return _fake_resp()
with (
patch("core.huya.dfp_register._load_chain", return_value=_dummy_chain()),
patch("core.huya.dfp_register._post", side_effect=spy_post),
):
t1, t2, t5 = register_device(fingerprint=None)
assert (t1, t2, t5) == ("a" * 32, "A" * 180, "b" * 40)
# getDfpConfig -> selectOperator -> dfpReport 的 content-type 序列
assert calls == [
"application/octet-stream",
"application/x-wup",
"application/octet-stream",
]
def test_select_operator_injects_fingerprint(self):
captured = []
def spy_post(
body, content_type="application/octet-stream", timeout=20, proxies=None
):
captured.append((body, content_type))
return _fake_resp()
new_fp = b"0" * 20 + b"f" * 20
with (
patch("core.huya.dfp_register._load_chain", return_value=_dummy_chain()),
patch("core.huya.dfp_register._post", side_effect=spy_post),
):
register_device(fingerprint=new_fp.decode("ascii"))
sel_body, sel_ct = captured[1]
assert sel_ct == "application/x-wup"
assert new_fp in sel_body, "selectOperator 应注入当前账号画像 fingerprint"
assert _GOLDEN_FP not in sel_body
# dfpReport 请求体为随机 cw 结构 (零设备生成)
dfp_body, dfp_ct = captured[2]
assert len(dfp_body) == 4226
assert dfp_ct == "application/octet-stream"
def test_http_failure_raises(self):
"""HTTP 层异常由 _post 转换为 DfpRegistrationError 后向上传播 (不静默)。"""
with (
patch("core.huya.dfp_register._load_chain", return_value=_dummy_chain()),
patch(
"core.huya.dfp_register.requests.post",
side_effect=requests.RequestException("connection reset"),
),
pytest.raises(DfpRegistrationError),
):
register_device()