ksjsb/tests/test_main_device_profile.py
2026-07-30 21:38:10 +08:00

1194 lines
47 KiB
Python

import json
import os
import tempfile
import unittest
import urllib.parse
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from core.device_profile import DeviceProfileGenerator, save_device_profile
from core.h5_jsbridge import build_h5_sign_input
from core.h5_sig3 import h5_sig3_crc32_from_sign_input, parse_h5_sig3
from core.weapon_kas import WeaponProofProvider
from main import (
AD_FETCH_PAYLOADS,
API_HEADERS,
DEFAULT_OAID,
KsNebulaClient,
ResponseRecord,
args_to_device_options,
build_parser,
cookie_from_env,
describe_local_algorithm_stack,
is_business_ok,
)
class MainDeviceProfileTests(unittest.TestCase):
def test_cookie_from_env_overlays_device_profile(self):
profile = DeviceProfileGenerator(seed=20260711).new_profile()
profile.apply_cloud_identity(
"ANDROID_69646c9107f45c63",
2,
"DFPB39C1B79D15E65AA00E29C724073B1578784BDC532027CF94846A0C145787",
)
with tempfile.TemporaryDirectory() as tmp:
profile_path = Path(tmp) / "device.json"
save_device_profile(profile, profile_path)
with patch.dict(
os.environ,
{
"KS_COOKIE": "userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
"KS_CLIENT_SALT": "salt",
},
clear=True,
):
full_cookie, client_salt, cookie_dict = cookie_from_env(device_profile_path=profile_path)
self.assertEqual(client_salt, "salt")
self.assertIn(f"did={profile.did}", full_cookie)
self.assertEqual(cookie_dict["did"], profile.did)
self.assertEqual(cookie_dict["oDid"], profile.o_did)
self.assertEqual(cookie_dict["rdid"], profile.rdid)
self.assertEqual(cookie_dict["egid"], profile.egid)
self.assertEqual(cookie_dict["oaid"], profile.runtime_hints.oaid)
self.assertEqual(cookie_dict["cdid_tag"], "2")
self.assertEqual(cookie_dict["token"], "TOKEN")
def test_parser_accepts_device_profile(self):
args = build_parser().parse_args(["--device-profile", "out/device.json"])
self.assertEqual(args.device_profile, "out/device.json")
def test_parser_accepts_in_memory_device_options(self):
args = build_parser().parse_args(
[
"--memory-device",
"--device-low-coin-threshold",
"20",
"--device-max-switches",
"2",
]
)
self.assertTrue(args.memory_device)
self.assertEqual(args.device_low_coin_threshold, 20)
self.assertEqual(args.device_max_switches, 2)
def test_parser_accepts_normal_ad_count(self):
args = build_parser().parse_args(["--normal-ad-count", "3"])
self.assertEqual(args.normal_ad_count, 3)
def test_parser_accepts_skip_ad_reward_threshold(self):
args = build_parser().parse_args(["--skip-ad-reward-leq", "1"])
self.assertEqual(args.skip_ad_reward_leq, 1)
def test_parser_accepts_print_local_algorithms(self):
args = build_parser().parse_args(["--print-algorithms"])
self.assertTrue(args.print_algorithms)
def test_local_algorithm_stack_contains_no_remote_signer(self):
lines = describe_local_algorithm_stack(
h5_sig3_mode="pure",
h5_kww_mode="pure",
h5_kws_mode="default",
)
text = "\n".join(lines)
self.assertIn("sig=core.sig", text)
self.assertIn("api_sig3=core.sig3", text)
self.assertIn("reward_fetch=core.reward_request", text)
self.assertIn("h5_sig3=core.h5_sig3", text)
self.assertNotIn("http://", text.lower())
self.assertNotIn("https://", text.lower())
self.assertNotIn("youfak.dev", text.lower())
def test_parser_accepts_server_device_alias(self):
args = build_parser().parse_args(["--server-device"])
self.assertTrue(args.server_device)
def test_server_device_conflicts_with_no_device_online(self):
args = build_parser().parse_args(["--server-device", "--no-device-online"])
with self.assertRaises(SystemExit):
args_to_device_options(args)
def test_cookie_from_env_reads_first_ksck_account(self):
"""任务运行环境支持单个 ksck 变量,多个账号按换行分隔。"""
with patch.dict(
os.environ,
{
"ksck": (
"一号#userId=1; ud=1; did=DID_ONE; "
"kuaishou.api_st=TOKEN_ONE; token=TOKEN_ONE#salt_one\r\n"
"二号#userId=2; ud=2; did=DID_TWO; "
"kuaishou.api_st=TOKEN_TWO; token=TOKEN_TWO#salt_two"
),
"KS_COOKIE": "userId=OLD; did=OLD_DID; kuaishou.api_st=OLD_TOKEN",
"KS_CLIENT_SALT": "old_salt",
},
clear=True,
):
full_cookie, client_salt, cookie_dict = cookie_from_env()
self.assertEqual(client_salt, "salt_one")
self.assertIn("did=DID_ONE", full_cookie)
self.assertEqual(cookie_dict["userId"], "1")
self.assertEqual(cookie_dict["did"], "DID_ONE")
self.assertEqual(cookie_dict["kuaishou.api_st"], "TOKEN_ONE")
def test_memory_device_overlays_cookie_without_writing_profile_file(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=1234,
)
self.assertIsNotNone(client.device_profile)
self.assertEqual(client.cookie_dict["did"], client.device_profile.did)
self.assertEqual(client.cookie_dict["token"], "TOKEN")
self.assertIn(f"did={client.device_profile.did}", client.full_cookie)
self.assertEqual(client._api_params()["egid"], client.device_profile.egid)
self.assertEqual(client._device_oaid(), client.device_profile.runtime_hints.oaid)
self.assertEqual(client._api_params()["socName"], client.device_profile.soc_name)
self.assertEqual(client._api_params()["boardPlatform"], client.device_profile.board_platform)
self.assertEqual(client._api_params()["max_memory"], str(client.device_profile.max_memory))
self.assertEqual(client._api_params()["deviceBit"], client.device_profile.device_bit)
def test_memory_device_online_bootstrap_failure_falls_back_to_local_profile(self):
with patch(
"main.run_online_bootstrap",
side_effect=RuntimeError("dfp unavailable"),
):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
)
self.assertIsNotNone(client.device_profile)
self.assertEqual(client.cookie_dict["did"], client.device_profile.did)
self.assertEqual(client.cookie_dict["egid"], client.device_profile.egid)
self.assertNotEqual(client.cookie_dict["did"], "ANDROID_oldoldoldold")
def test_memory_device_online_partial_failure_restores_local_profile(self):
def mutate_then_fail(profile, timeout):
del timeout
profile.apply_cloud_identity("ANDROID_f05497e9cef09a7f", 2)
raise RuntimeError("gdfp report unavailable")
with patch("main.run_online_bootstrap", side_effect=mutate_then_fail):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
)
self.assertNotEqual(client.device_profile.did, "ANDROID_f05497e9cef09a7f")
self.assertEqual(client.device_profile.did, client.device_profile.local_did)
self.assertEqual(client.cookie_dict["did"], client.device_profile.local_did)
def test_memory_device_online_did_without_egid_restores_local_profile(self):
def did_only_bootstrap(profile, timeout):
del timeout
profile.apply_cloud_identity("ANDROID_f05497e9cef09a7f", 2)
return {"identity": {"did": "ANDROID_f05497e9cef09a7f", "cdid_tag": 2, "egid": ""}}
with patch("main.run_online_bootstrap", side_effect=did_only_bootstrap):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
)
self.assertNotEqual(client.device_profile.did, "ANDROID_f05497e9cef09a7f")
self.assertEqual(client.device_profile.did, client.device_profile.local_did)
self.assertEqual(client.cookie_dict["egid"], client.device_profile.egid)
def test_server_device_mode_accepts_online_bootstrap_with_egid(self):
def successful_bootstrap(profile, timeout):
del timeout
profile.apply_cloud_identity(
"ANDROID_f05497e9cef09a7f",
2,
"DFP" + "A" * 61,
)
return {
"identity": {
"did": "ANDROID_f05497e9cef09a7f",
"cdid_tag": 2,
"egid": "DFP" + "A" * 61,
}
}
with patch("main.run_online_bootstrap", side_effect=successful_bootstrap):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
)
self.assertEqual(client.device_profile.did, "ANDROID_f05497e9cef09a7f")
self.assertEqual(client.cookie_dict["did"], "ANDROID_f05497e9cef09a7f")
self.assertEqual(client.cookie_dict["egid"], "DFP" + "A" * 61)
def test_memory_device_keeps_h5_oaid_bound_to_original_cookie(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=2468,
)
oaid = client.device_profile.runtime_hints.oaid
task_params = client._task_list_params()
treasure_body = json.loads(client._treasure_open_body())
self.assertEqual(client._device_oaid(), oaid)
self.assertEqual(task_params["oaid"], DEFAULT_OAID)
self.assertEqual(treasure_body["oaid"], DEFAULT_OAID)
self.assertEqual(treasure_body["imei"], "")
self.assertEqual(treasure_body["idfa"], "")
def test_memory_device_preserves_h5_cookie_for_sig3_and_headers(self):
original_cookie = {
"kpn": "NEBULA",
"kpf": "ANDROID_PHONE",
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"egid": "DFPOLD",
"oDid": "ANDROID_OLD_ODID",
"rdid": "ANDROID_OLD_RDID",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
}
client = KsNebulaClient(
full_cookie="; ".join(f"{k}={v}" for k, v in original_cookie.items()),
client_salt="salt",
cookie_dict=dict(original_cookie),
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=1357,
)
query = {"source": "bottom_guide_first"}
generated = client._next_h5_sig3("treasure_info", query=query)
parsed = parse_h5_sig3(generated)
expected_input = build_h5_sign_input(original_cookie, query=query)
self.assertNotEqual(client.cookie_dict["did"], original_cookie["did"])
self.assertEqual(client.h5_cookie_dict["did"], original_cookie["did"])
self.assertEqual(parsed.crc32, h5_sig3_crc32_from_sign_input(expected_input))
captured = {}
def fake_request(label, title, request_id, method, url, headers, body=None):
captured["headers"] = headers
return ResponseRecord(label, title, request_id, 200, True, url, {"result": 1}, "")
client._request = fake_request
client.sign_in_resource()
self.assertIn("did=ANDROID_oldoldoldold", captured["headers"]["Cookie"])
self.assertNotIn(client.cookie_dict["did"], captured["headers"]["Cookie"])
def test_low_coin_report_rotates_memory_device(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=5678,
device_low_coin_threshold=20,
device_max_switches=1,
)
first_did = client.cookie_dict["did"]
low_coin_record = ResponseRecord(
label="normal_ad_report",
title="看广告得金币上报",
request_id=2700,
status=200,
ok=True,
url="",
response_json={"result": 1, "data": {"neoAmount": 5}},
response_text="",
)
rotated = client.maybe_rotate_device_for_record(low_coin_record)
self.assertTrue(rotated)
self.assertNotEqual(client.cookie_dict["did"], first_did)
def test_low_expected_reward_skip_rotates_memory_device(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=6789,
device_low_coin_threshold=20,
device_max_switches=1,
)
first_did = client.cookie_dict["did"]
skipped_record = ResponseRecord(
label="normal_ad_skip",
title="看广告得金币跳过",
request_id=2637,
status=None,
ok=True,
url="",
response_json={"skipped": True, "reason": "low_expected_reward", "expectedReward": 1},
response_text="",
)
rotated = client.maybe_rotate_device_for_record(skipped_record)
self.assertTrue(rotated)
self.assertNotEqual(client.cookie_dict["did"], first_did)
def test_task_countdown_skip_does_not_rotate_memory_device(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=7890,
device_low_coin_threshold=20,
device_max_switches=1,
)
first_did = client.cookie_dict["did"]
skipped_record = ResponseRecord(
label="normal_ad_skip",
title="看广告得金币跳过",
request_id=2637,
status=None,
ok=True,
url="",
response_json={"skipped": True, "reason": "task_countdown", "countdownMs": 2411231},
response_text="",
)
rotated = client.maybe_rotate_device_for_record(skipped_record)
self.assertFalse(rotated)
self.assertEqual(client.cookie_dict["did"], first_did)
def test_antispam_and_signature_failure_are_not_business_ok(self):
self.assertFalse(is_business_ok(200, {"result": 50, "error_msg": "签名验证失败"}))
self.assertFalse(is_business_ok(200, {"result": 1, "errorMsg": "ANTISPAM_REQUEST"}))
self.assertFalse(is_business_ok(200, {"result": 1, "errorMsg": "INVALID_REQUEST"}))
def test_api_requests_use_one_reused_okhttp_session(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_device; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_device",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
)
calls = []
class FakeApiSession:
def request(self, method, url, **kwargs):
calls.append((method, url, kwargs))
return SimpleNamespace(
status_code=200,
content=b'{"result":1}',
headers={},
)
with patch(
"main.create_http_session",
return_value=FakeApiSession(),
create=True,
) as create_session, patch(
"main.urllib.request.urlopen",
side_effect=AssertionError("API request must not use urllib"),
):
first = client._request(
"first",
"first",
1,
"POST",
"https://api.e.kuaishou.com/rest/e/reward/mixed/ad?a=1",
{"Content-Type": "application/x-www-form-urlencoded"},
b"x=1",
)
second = client._request(
"second",
"second",
2,
"POST",
"https://api.e.kuaishou.com/rest/r/ad/task/report?a=2",
{"Content-Type": "application/x-www-form-urlencoded"},
b"x=2",
)
self.assertTrue(first.ok)
self.assertTrue(second.ok)
create_session.assert_called_once_with("okhttp4-android10")
self.assertEqual([item[0] for item in calls], ["POST", "POST"])
self.assertEqual([item[2]["data"] for item in calls], [b"x=1", b"x=2"])
expected_headers = [
WeaponProofProvider(API_HEADERS["kaw"])(item[1])
for item in calls
]
self.assertEqual(
[item[2]["headers"]["kaw"] for item in calls],
[item["kaw"] for item in expected_headers],
)
self.assertEqual(
[item[2]["headers"]["kas"] for item in calls],
[item["kas"] for item in expected_headers],
)
self.assertNotEqual(
calls[0][2]["headers"]["kas"],
calls[1][2]["headers"]["kas"],
)
def test_fetch_ad_builds_dynamic_reward_body(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=False,
device_seed=4321,
)
captured = {}
def fake_request(label, title, request_id, method, url, headers, body=None):
captured["body"] = body.decode("utf-8")
captured["url"] = url
return ResponseRecord(label, title, request_id, 200, False, url, {"result": 1, "feeds": []}, "")
client._request = fake_request
client.fetch_ad("normal")
body = dict(urllib.parse.parse_qsl(captured["body"], keep_blank_values=True))
self.assertIn("encData", body)
self.assertIn("sign", body)
self.assertNotEqual(body["encData"], AD_FETCH_PAYLOADS["normal"]["encData"])
self.assertNotEqual(body["sign"], AD_FETCH_PAYLOADS["normal"]["sign"])
def test_fetch_ad_passes_profile_oaid_to_reward_str_e(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=False,
device_seed=8642,
)
captured = {}
def fake_request(label, title, request_id, method, url, headers, body=None):
return ResponseRecord(label, title, request_id, 200, False, url, {"result": 1, "feeds": []}, "")
def fake_build_reward_str_e(**kwargs):
captured.update(kwargs)
return b"{}"
client._request = fake_request
with patch("main.build_reward_str_e", side_effect=fake_build_reward_str_e), patch(
"main.build_reward_body",
return_value=SimpleNamespace(enc_data="ENC", sign="SIGN", sign_counter=1),
):
client.fetch_ad("normal")
self.assertEqual(captured["oaid"], client.device_profile.runtime_hints.oaid)
def test_h5_sig3_defaults_to_pure_python_digest(self):
cookie_dict = {
"kpn": "NEBULA",
"kpf": "ANDROID_PHONE",
"userId": "1579452490",
"did": "ANDROID_aaaaaaaaaaaaaaaa",
"egid": "DFPBBBB",
"oDid": "ANDROID_cccccccccccccccc",
"rdid": "ANDROID_dddddddddddddddd",
}
with patch.dict(os.environ, {}, clear=True), patch(
"main.H5JsBridgeEncoder",
side_effect=AssertionError("pure mode must not start JS bridge"),
):
client = KsNebulaClient(
full_cookie="; ".join(f"{k}={v}" for k, v in cookie_dict.items()),
client_salt="salt",
cookie_dict=dict(cookie_dict),
timeout=30,
dry_run=True,
)
query = {"source": "bottom_guide_first"}
generated = client._next_h5_sig3("treasure_info", query=query)
sign_input = build_h5_sign_input(cookie_dict, query=query)
parsed = parse_h5_sig3(generated)
self.assertTrue(parsed.ok)
self.assertEqual(parsed.crc32, h5_sig3_crc32_from_sign_input(sign_input))
self.assertEqual(client.h5_sig3_mode, "pure")
def test_h5_kww_pure_mode_does_not_start_node_bridge(self):
with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch(
"main.H5KwwGenerator",
side_effect=AssertionError("pure kww mode must not start Node bridge"),
) as node_bridge:
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
)
headers = client._h5_headers(
"sign_in_report",
url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report",
method="GET",
)
node_bridge.assert_not_called()
self.assertEqual(client.h5_kww_mode, "pure")
self.assertTrue(headers["kww"].startswith("PnGU+9+Y8008S+nH"))
self.assertEqual(len(headers["kww"]), 174)
def test_h5_kww_pure_mode_syncs_storage_fields_into_cookie(self):
fake_result = SimpleNamespace(kww="PnGU" + "A" * 170, kwfcv1="3", kwfv1="PnGU" + "A" * 170)
fake_generator = SimpleNamespace(get=lambda **_: fake_result, close=lambda: None)
with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch(
"main.PureH5KwwGenerator",
return_value=fake_generator,
):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
)
headers = client._h5_headers(
"sign_in_report",
url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report",
method="GET",
)
self.assertEqual(headers["kww"], fake_result.kww)
self.assertEqual(client.h5_cookie_dict["kwfv1"], fake_result.kwfv1)
self.assertEqual(client.h5_cookie_dict["kwfcv1"], fake_result.kwfcv1)
self.assertEqual(client.h5_cookie_dict["kwpsecproductname"], "kuaishou-growth")
self.assertIn(f"kwfv1={fake_result.kwfv1}", headers["Cookie"])
self.assertIn("kwfcv1=3", client.h5_full_cookie)
def test_h5_headers_fills_kws_default_ticket_when_missing(self):
fake_kww = SimpleNamespace(kww="PnGU" + "B" * 170, kwfcv1="2", kwfv1="PnGU" + "B" * 170)
fake_kww_generator = SimpleNamespace(get=lambda **_: fake_kww, close=lambda: None)
fake_kws_ticket = SimpleNamespace(
kwpsecproductname="kuaishou-growth",
kwssectoken="S" * 64,
kwscode="K" + "C" * 217 + "==",
kwfv1="K" + "F" * 217 + "==",
kww="K" + "F" * 217 + "==",
cookie_fields=lambda include_fingerprint=True: {
"kwpsecproductname": "kuaishou-growth",
"kwssectoken": "S" * 64,
"kwscode": "K" + "C" * 217 + "==",
**({"kwfv1": "K" + "F" * 217 + "=="} if include_fingerprint else {}),
},
)
with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch(
"main.PureH5KwwGenerator",
return_value=fake_kww_generator,
), patch("main.build_h5_kws_default_ticket", return_value=fake_kws_ticket) as build_kws:
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
)
headers = client._h5_headers(
"sign_in_report",
url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report",
method="GET",
)
build_kws.assert_called_once()
self.assertEqual(client.h5_cookie_dict["kwssectoken"], "S" * 64)
self.assertEqual(client.h5_cookie_dict["kwscode"], "K" + "C" * 217 + "==")
self.assertEqual(client.h5_cookie_dict["kwfv1"], fake_kww.kwfv1)
self.assertIn("kwssectoken=", headers["Cookie"])
self.assertIn("kwscode=", headers["Cookie"])
def test_h5_headers_can_fill_kws_server_script_ticket_from_env(self):
fake_kww = SimpleNamespace(kww="PnGU" + "E" * 170, kwfcv1="2", kwfv1="PnGU" + "E" * 170)
fake_kww_generator = SimpleNamespace(get=lambda **_: fake_kww, close=lambda: None)
fake_script_ticket = {
"kwpsecproductname": "kuaishou-growth",
"kwssectoken": "SERVER_SEC_TOKEN",
"kwscode": "04dd303d63222chdfec0087d058fb1c4c5f2eb16eefgce98b2a503578e5f8433",
}
with patch.dict(
os.environ,
{
"KS_H5_KWW_MODE": "pure",
"KS_H5_KWS_MODE": "script",
"KS_H5_KWS_SEC_TOKEN": "SERVER_SEC_TOKEN",
},
clear=True,
), patch(
"main.PureH5KwwGenerator",
return_value=fake_kww_generator,
), patch(
"main.build_h5_kws_script_ticket",
return_value=fake_script_ticket,
) as build_script, patch(
"main.build_h5_kws_default_ticket",
side_effect=AssertionError("script mode should use secToken + sign script"),
):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
)
headers = client._h5_headers(
"sign_in_report",
url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report",
method="GET",
)
build_script.assert_called_once_with(
sec_token="SERVER_SEC_TOKEN",
product_name="kuaishou-growth",
)
self.assertEqual(client.h5_cookie_dict["kwssectoken"], "SERVER_SEC_TOKEN")
self.assertEqual(
client.h5_cookie_dict["kwscode"],
"04dd303d63222chdfec0087d058fb1c4c5f2eb16eefgce98b2a503578e5f8433",
)
self.assertIn("kwssectoken=SERVER_SEC_TOKEN", headers["Cookie"])
self.assertIn("kwscode=04dd303d63222chdfec0087d058fb1c4c5f2eb16eefgce98b2a503578e5f8433", headers["Cookie"])
def test_h5_headers_keeps_existing_kws_ticket(self):
fake_kww = SimpleNamespace(kww="PnGU" + "D" * 170, kwfcv1="2", kwfv1="PnGU" + "D" * 170)
fake_kww_generator = SimpleNamespace(get=lambda **_: fake_kww, close=lambda: None)
with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch(
"main.PureH5KwwGenerator",
return_value=fake_kww_generator,
), patch(
"main.build_h5_kws_default_ticket",
side_effect=AssertionError("existing KWS ticket must be preserved"),
):
client = KsNebulaClient(
full_cookie=(
"userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; "
"kuaishou.api_st=API_ST; kwssectoken=OLD_SEC; kwscode=OLD_CODE"
),
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
"kwssectoken": "OLD_SEC",
"kwscode": "OLD_CODE",
},
timeout=30,
dry_run=True,
)
headers = client._h5_headers(
"sign_in_report",
url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report",
method="GET",
)
self.assertEqual(client.h5_cookie_dict["kwssectoken"], "OLD_SEC")
self.assertEqual(client.h5_cookie_dict["kwscode"], "OLD_CODE")
self.assertIn("kwssectoken=OLD_SEC", headers["Cookie"])
self.assertIn("kwscode=OLD_CODE", headers["Cookie"])
def test_ad_sequence_skips_report_when_fetch_fails(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
)
client.fetch_ad = lambda kind: ResponseRecord(
f"{kind}_ad_fetch",
"看广告得金币拉取",
2637,
200,
False,
"",
{"result": 1, "errorMsg": "INVALID_REQUEST"},
"",
)
def fail_report(kind, ad_wait):
raise AssertionError("report should not run after failed ad fetch")
client._wait_then_report = fail_report
records = client.run_ad_sequence("normal", 0)
self.assertEqual(len(records), 1)
self.assertEqual(records[0].label, "normal_ad_fetch")
def test_ad_sequence_skips_report_when_expected_reward_is_too_low(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
)
client.ad_materials["normal"] = object()
client.fetch_ad = lambda kind: ResponseRecord(
f"{kind}_ad_fetch",
"看广告得金币拉取",
2637,
200,
True,
"",
{
"result": 1,
"feeds": [
{
"ad": {
"adDataV2": {
"templateDatas": [
{"data": json.dumps({"neoCount": 1})},
]
}
}
}
],
},
"",
)
def fail_report(kind, ad_wait):
raise AssertionError("report should not run for skipped low reward ad")
client._wait_then_report = fail_report
records = client.run_ad_sequence("normal", 0, skip_reward_leq=1)
self.assertEqual(len(records), 2)
self.assertEqual(records[1].label, "normal_ad_skip")
self.assertTrue(records[1].ok)
self.assertEqual(records[1].response_json["expectedReward"], 1)
def test_normal_ad_sequence_skips_fetch_during_task_countdown(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
)
client._update_ad_fetch_contexts(
{
"data": {
"dailyTasks": [
{
"name": "看广告得金币",
"linkUrl": "eyJidXNpbmVzc0lkIjo2NzIsInBhZ2VJZCI6MTExMDEsInN1YlBhZ2VJZCI6MTAwMDI2MzY3LCJwb3NJZCI6MjQwNjd9",
"extParam": {
"descCountDown": 2411231,
"buttonCountDownToast": "休息一下,稍后再来领取金币",
},
}
]
}
}
)
def fail_fetch(kind):
raise AssertionError("normal ad fetch should not run during countdown")
client.fetch_ad = fail_fetch
records = client.run_ad_sequence("normal", 0)
self.assertEqual(len(records), 1)
self.assertEqual(records[0].label, "normal_ad_skip")
self.assertEqual(records[0].response_json["reason"], "task_countdown")
self.assertEqual(records[0].response_json["countdownMs"], 2411231)
def test_run_all_once_repeats_normal_ad_count(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
)
called = []
def ok_record(label, title, request_id):
return ResponseRecord(label, title, request_id, 200, True, "", {"result": 1}, "")
client.query_balance = lambda: ok_record("login_check", "登录态/余额检查", 1286)
client.task_list = lambda: ok_record("task_list", "任务列表", 1383)
client.sign_in_resource = lambda: ok_record("sign_in_resource", "签到资源", 1476)
client.sign_in = lambda: ok_record("sign_in_report", "签到上报", 1633)
client.treasure_box_info = lambda: ok_record("treasure_info", "宝箱信息", 1376)
client.open_treasure_box = lambda: ok_record("treasure_open", "开宝箱", 2105)
client.final_balance = lambda: ok_record("final_balance", "最终余额", 2709)
def fake_ad_sequence(kind, ad_wait):
called.append((kind, ad_wait))
return [
ok_record(f"{kind}_ad_fetch", f"{kind} 拉取", 100),
ok_record(f"{kind}_ad_report", f"{kind} 上报", 101),
]
client.run_ad_sequence = fake_ad_sequence
with tempfile.TemporaryDirectory() as tmp:
exit_code = client.run_all_once(ad_wait=0, out_dir=Path(tmp), normal_ad_count=3)
self.assertEqual(exit_code, 0)
self.assertEqual([kind for kind, _ in called], ["sign_in", "treasure", "normal", "normal", "normal"])
def test_parser_accepts_rotate_h5_and_strict_device_flags(self):
args = build_parser().parse_args(["--rotate-h5-device", "--strict-device-online"])
self.assertTrue(args.rotate_h5_device)
self.assertTrue(args.strict_device_online)
def test_args_to_device_options_propagates_rotate_and_strict(self):
args = build_parser().parse_args(
["--memory-device", "--rotate-h5-device", "--strict-device-online"]
)
options = args_to_device_options(args)
self.assertTrue(options.rotate_h5_device)
self.assertTrue(options.strict_device_online)
def test_rotate_h5_device_syncs_h5_cookie_with_memory_device(self):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=1357,
rotate_h5_device=True,
)
self.assertEqual(client.h5_cookie_dict["did"], client.device_profile.did)
self.assertEqual(client.h5_cookie_dict["egid"], client.device_profile.egid)
self.assertEqual(client.h5_cookie_dict["oaid"], client.device_profile.runtime_hints.oaid)
self.assertIn(f"did={client.device_profile.did}", client.h5_full_cookie)
self.assertNotIn("did=ANDROID_oldoldoldold", client.h5_full_cookie)
def test_rotate_h5_device_carries_new_device_in_h5_request(self):
original_cookie = {
"kpn": "NEBULA",
"kpf": "ANDROID_PHONE",
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"egid": "DFPOLD",
"oDid": "ANDROID_OLD_ODID",
"rdid": "ANDROID_OLD_RDID",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
}
client = KsNebulaClient(
full_cookie="; ".join(f"{k}={v}" for k, v in original_cookie.items()),
client_salt="salt",
cookie_dict=dict(original_cookie),
timeout=30,
dry_run=True,
memory_device=True,
device_online=False,
device_seed=1357,
rotate_h5_device=True,
)
captured = {}
def fake_request(label, title, request_id, method, url, headers, body=None):
captured["headers"] = headers
return ResponseRecord(label, title, request_id, 200, True, url, {"result": 1}, "")
client._request = fake_request
client.sign_in_resource()
cookie_header = captured["headers"]["Cookie"]
self.assertIn(f"did={client.device_profile.did}", cookie_header)
self.assertNotIn("did=ANDROID_oldoldoldold", cookie_header)
def test_strict_device_online_raises_on_bootstrap_failure(self):
with patch("main.run_online_bootstrap", side_effect=RuntimeError("dfp unavailable")):
with self.assertRaises(SystemExit):
KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
strict_device_online=True,
)
def test_strict_device_online_raises_when_bootstrap_returns_no_egid(self):
def did_only_bootstrap(profile, timeout):
del timeout
profile.apply_cloud_identity("ANDROID_f05497e9cef09a7f", 2)
return {"identity": {"did": "ANDROID_f05497e9cef09a7f", "cdid_tag": 2, "egid": ""}}
with patch("main.run_online_bootstrap", side_effect=did_only_bootstrap):
with self.assertRaises(SystemExit):
KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
strict_device_online=True,
)
def test_strict_device_online_midrun_failure_keeps_current_device(self):
calls = {"n": 0}
def bootstrap(profile, timeout):
del timeout
calls["n"] += 1
if calls["n"] == 1:
profile.apply_cloud_identity(
"ANDROID_f05497e9cef09a7f",
2,
"DFP" + "A" * 61,
)
return {
"identity": {
"did": "ANDROID_f05497e9cef09a7f",
"cdid_tag": 2,
"egid": "DFP" + "A" * 61,
}
}
raise RuntimeError("gdfp report unavailable")
with patch("main.run_online_bootstrap", side_effect=bootstrap):
client = KsNebulaClient(
full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST",
client_salt="salt",
cookie_dict={
"userId": "1579452490",
"did": "ANDROID_oldoldoldold",
"token": "TOKEN",
"kuaishou.api_st": "API_ST",
},
timeout=30,
dry_run=False,
memory_device=True,
device_online=True,
device_seed=97531,
strict_device_online=True,
device_low_coin_threshold=20,
device_max_switches=1,
)
first_did = client.cookie_dict["did"]
low_coin_record = ResponseRecord(
label="normal_ad_report",
title="看广告得金币上报",
request_id=2700,
status=200,
ok=True,
url="",
response_json={"result": 1, "data": {"neoAmount": 5}},
response_text="",
)
rotated = client.maybe_rotate_device_for_record(low_coin_record)
self.assertTrue(rotated)
self.assertEqual(client.cookie_dict["did"], first_did)
self.assertEqual(calls["n"], 2)
if __name__ == "__main__":
unittest.main()