- Add tests/crawler_core/test_boss_sign.py: 13 tests for BossSign, _compute_checksum, _generate_uuid - Add tests/crawler_core/test_qcwy_sign.py: 10 tests for Job51Sign and SIGN_KEY - Add tests/crawler_core/test_zhilian_sign.py: 13 tests for ZhilianSign - Add conftest.py at project root to add project root to sys.path - Update pyproject.toml with [tool.pytest.ini_options] pythonpath config - Fix crawler_core/__init__.py: wrap heavy-dep imports in try/except so sign subpackages are importable in lightweight envs without requests_go installed - Remove tests/crawler_core/__init__.py to prevent namespace shadowing of crawler_core package
117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
"""Unit tests for crawler_core.zhilian.sign — ZhilianSign.
|
|
|
|
All tests are pure function assertions: no HTTP, no network, no mocks.
|
|
"""
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
|
|
import re
|
|
import pytest
|
|
from crawler_core.zhilian.sign import ZhilianSign
|
|
|
|
EXPECTED_HEADER_KEYS = {
|
|
"x-zp-at", "x-zp-rt", "x-zp-action-id", "x-zp-page-code",
|
|
"x-zp-version", "x-zp-channel", "x-zp-platform", "x-zp-device-id",
|
|
"x-zp-business-system",
|
|
}
|
|
|
|
EXPECTED_PARAM_KEYS = {"at", "rt", "channel", "platform", "version", "d"}
|
|
|
|
|
|
class TestZhilianSignInit:
|
|
def test_defaults(self):
|
|
sign = ZhilianSign()
|
|
assert sign.at == ""
|
|
assert sign.rt == ""
|
|
assert sign.version == "4.1.259"
|
|
assert sign.channel == "wxxiaochengxu"
|
|
assert sign.platform == "12"
|
|
assert sign.device_id # auto-generated, not empty
|
|
|
|
def test_custom_tokens(self):
|
|
sign = ZhilianSign(at="at_token", rt="rt_token")
|
|
assert sign.at == "at_token"
|
|
assert sign.rt == "rt_token"
|
|
|
|
def test_custom_device_id(self):
|
|
sign = ZhilianSign(device_id="CUSTOM-DEVICE-ID")
|
|
assert sign.device_id == "CUSTOM-DEVICE-ID"
|
|
|
|
def test_auto_device_id_is_uuid4_format(self):
|
|
sign = ZhilianSign()
|
|
uuid_pattern = r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$'
|
|
assert re.match(uuid_pattern, sign.device_id), \
|
|
f"device_id not UUID4 format: {sign.device_id}"
|
|
|
|
|
|
class TestZhilianSignHeaders:
|
|
def setup_method(self):
|
|
self.sign = ZhilianSign(at="at123", rt="rt456")
|
|
|
|
def test_keys_exactly_nine(self):
|
|
headers = self.sign.sign_headers()
|
|
assert set(headers.keys()) == EXPECTED_HEADER_KEYS, \
|
|
f"Header keys wrong: {set(headers.keys())}"
|
|
|
|
def test_business_system_is_73(self):
|
|
headers = self.sign.sign_headers()
|
|
assert headers["x-zp-business-system"] == "73"
|
|
|
|
def test_tokens_reflected(self):
|
|
headers = self.sign.sign_headers()
|
|
assert headers["x-zp-at"] == "at123"
|
|
assert headers["x-zp-rt"] == "rt456"
|
|
|
|
def test_action_id_is_uuid4_format(self):
|
|
headers = self.sign.sign_headers()
|
|
action_id = headers["x-zp-action-id"]
|
|
uuid_pattern = r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$'
|
|
assert re.match(uuid_pattern, action_id), \
|
|
f"action_id not UUID4 format: {action_id}"
|
|
|
|
def test_action_id_unique_per_call(self):
|
|
h1 = self.sign.sign_headers()
|
|
h2 = self.sign.sign_headers()
|
|
assert h1["x-zp-action-id"] != h2["x-zp-action-id"], \
|
|
"action_id must be freshly generated on each call"
|
|
|
|
def test_device_id_in_headers(self):
|
|
headers = self.sign.sign_headers()
|
|
assert headers["x-zp-device-id"] == self.sign.device_id
|
|
|
|
|
|
class TestZhilianSignParams:
|
|
def setup_method(self):
|
|
self.sign = ZhilianSign(at="at789", rt="rt012", device_id="DEV-ID")
|
|
|
|
def test_keys_exactly_six(self):
|
|
params = self.sign.sign_params()
|
|
assert set(params.keys()) == EXPECTED_PARAM_KEYS, \
|
|
f"Param keys wrong: {set(params.keys())}"
|
|
|
|
def test_device_id_as_d(self):
|
|
params = self.sign.sign_params()
|
|
assert params["d"] == "DEV-ID"
|
|
|
|
def test_tokens_reflected(self):
|
|
params = self.sign.sign_params()
|
|
assert params["at"] == "at789"
|
|
assert params["rt"] == "rt012"
|
|
|
|
|
|
class TestZhilianGenerateUuid:
|
|
def test_uuid4_format(self):
|
|
uuid = ZhilianSign.generate_uuid()
|
|
uuid_pattern = r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$'
|
|
assert re.match(uuid_pattern, uuid), \
|
|
f"UUID not UUID4 format: {uuid}"
|
|
|
|
def test_uuid_length(self):
|
|
uuid = ZhilianSign.generate_uuid()
|
|
assert len(uuid) == 36, f"Expected 36 chars, got {len(uuid)}"
|
|
|
|
def test_uuid_version_4(self):
|
|
uuid = ZhilianSign.generate_uuid()
|
|
assert uuid[14] == "4", f"Version digit should be 4, got: {uuid[14]}"
|