239 lines
8.3 KiB
Python
239 lines
8.3 KiB
Python
import unittest
|
|
from contextlib import redirect_stderr, redirect_stdout
|
|
from io import StringIO
|
|
from types import ModuleType
|
|
from unittest.mock import MagicMock, call, patch
|
|
|
|
from core.device_profile import DeviceProfileGenerator
|
|
from core.sig3 import Kwsg10418State
|
|
from core.sms_login import LoginSession, RegionTicket
|
|
from tools import sms_login_cli
|
|
from tools.sms_login_cli import build_parser
|
|
|
|
|
|
class SmsLoginCliTests(unittest.TestCase):
|
|
def test_parser_only_exposes_login_inputs_and_transport(self):
|
|
parser = build_parser()
|
|
option_strings = {
|
|
option
|
|
for action in parser._actions
|
|
for option in action.option_strings
|
|
}
|
|
|
|
self.assertEqual(
|
|
option_strings,
|
|
{
|
|
"-h",
|
|
"--help",
|
|
"--mobile",
|
|
"--code",
|
|
"--base-url",
|
|
"--transport",
|
|
},
|
|
)
|
|
|
|
def test_parser_rejects_removed_device_reuse_options(self):
|
|
parser = build_parser()
|
|
|
|
for removed_option in (
|
|
"--device-profile",
|
|
"--device-seed",
|
|
"--no-device-online",
|
|
"--app-fields",
|
|
"--dry-run",
|
|
"--out",
|
|
):
|
|
with self.subTest(option=removed_option):
|
|
with redirect_stderr(StringIO()), self.assertRaises(SystemExit):
|
|
parser.parse_args(["--mobile", "13800000000", removed_option])
|
|
|
|
def test_parser_rejects_mobile_placeholders(self):
|
|
parser = build_parser()
|
|
|
|
with redirect_stderr(StringIO()), self.assertRaises(SystemExit):
|
|
parser.parse_args(["--mobile", "MOBILE"])
|
|
|
|
def test_http_captcha_failure_does_not_open_a_browser(self):
|
|
self.assertFalse(hasattr(sms_login_cli, "complete_captcha_in_browser"))
|
|
self.assertFalse(hasattr(sms_login_cli, "webbrowser"))
|
|
with (
|
|
patch.object(sms_login_cli, "_solve_captcha_http", return_value=""),
|
|
redirect_stdout(StringIO()) as output,
|
|
):
|
|
token = sms_login_cli._complete_captcha_handoff(
|
|
"https://app.m.kuaishou.com/verify/captcha.html?key=1&type=7",
|
|
stage="mobileVerifyCode",
|
|
session=object(),
|
|
)
|
|
|
|
self.assertEqual(token, "")
|
|
self.assertIn("不启用浏览器", output.getvalue())
|
|
|
|
def test_http_captcha_diagnoses_known_result_codes(self):
|
|
self.assertEqual(
|
|
set(sms_login_cli._CAPTCHA_RESULT_HINTS),
|
|
{350001, 350002, 350005, 350009, 350014, 350017, 350029},
|
|
)
|
|
solver_module = ModuleType("tools.captcha_solver_http")
|
|
solver_module.solve_captcha = lambda **kwargs: {
|
|
"ok": False,
|
|
"stage": "verify",
|
|
"result": 350029,
|
|
"captcha_token": "",
|
|
"raw": {
|
|
"result": 350029,
|
|
"desc": "switch captcha type",
|
|
"type": "4",
|
|
"configPath": "/rest/zt/captcha/wordClick/config",
|
|
},
|
|
}
|
|
|
|
with (
|
|
patch.dict(
|
|
sms_login_cli.sys.modules,
|
|
{"tools.captcha_solver_http": solver_module},
|
|
),
|
|
redirect_stdout(StringIO()) as output,
|
|
):
|
|
token = sms_login_cli._solve_captcha_http(
|
|
"https://app.m.kuaishou.com/verify/captcha.html?key=1&type=7",
|
|
session=object(),
|
|
)
|
|
|
|
self.assertEqual(token, "")
|
|
self.assertIn("result=350029", output.getvalue())
|
|
self.assertIn("next_type=4", output.getvalue())
|
|
self.assertIn(
|
|
"config_path=/rest/zt/captcha/wordClick/config",
|
|
output.getvalue(),
|
|
)
|
|
|
|
def test_each_main_call_generates_and_registers_a_fresh_device(self):
|
|
profiles = [
|
|
DeviceProfileGenerator(seed=20260730).new_profile(),
|
|
DeviceProfileGenerator(seed=20260731).new_profile(),
|
|
]
|
|
generators = [MagicMock(), MagicMock()]
|
|
for generator, profile in zip(generators, profiles, strict=True):
|
|
generator.new_profile.return_value = profile
|
|
|
|
http_sessions = [MagicMock(), MagicMock()]
|
|
anonymous_regions = [
|
|
RegionTicket(uid="0", ticket="RT_ANON_1", status=200),
|
|
RegionTicket(uid="0", ticket="RT_ANON_2", status=200),
|
|
]
|
|
account_regions = [
|
|
RegionTicket(uid="101", ticket="RT_ACCOUNT_1", status=200),
|
|
RegionTicket(uid="102", ticket="RT_ACCOUNT_2", status=200),
|
|
]
|
|
login_sessions = [
|
|
LoginSession(
|
|
api_st="API_ST_1",
|
|
h5_st="H5_ST_1",
|
|
client_salt="SALT_1",
|
|
user_id="101",
|
|
mobile="13800000000",
|
|
),
|
|
LoginSession(
|
|
api_st="API_ST_2",
|
|
h5_st="H5_ST_2",
|
|
client_salt="SALT_2",
|
|
user_id="102",
|
|
mobile="13800000000",
|
|
),
|
|
]
|
|
|
|
output = StringIO()
|
|
with (
|
|
patch.object(
|
|
sms_login_cli,
|
|
"DeviceProfileGenerator",
|
|
side_effect=generators,
|
|
),
|
|
patch.object(sms_login_cli, "run_online_bootstrap") as bootstrap,
|
|
patch.object(
|
|
sms_login_cli,
|
|
"generate_profile_passport_account_image",
|
|
side_effect=("PASSPORT_1", "PASSPORT_2"),
|
|
) as generate_passport,
|
|
patch.object(sms_login_cli, "WeaponProofProvider"),
|
|
patch.object(
|
|
sms_login_cli,
|
|
"_new_sig3_state",
|
|
side_effect=(
|
|
Kwsg10418State(session_seed=1, counter=0x5F),
|
|
Kwsg10418State(session_seed=2, counter=0x5F),
|
|
),
|
|
),
|
|
patch.object(
|
|
sms_login_cli,
|
|
"create_http_session",
|
|
side_effect=http_sessions,
|
|
),
|
|
patch.object(
|
|
sms_login_cli,
|
|
"refresh_region_ticket",
|
|
side_effect=(
|
|
anonymous_regions[0],
|
|
account_regions[0],
|
|
anonymous_regions[1],
|
|
account_regions[1],
|
|
),
|
|
),
|
|
patch.object(
|
|
sms_login_cli,
|
|
"loginhelper_encrypt_mobile",
|
|
side_effect=("ENC_1", "ENC_2"),
|
|
),
|
|
patch.object(
|
|
sms_login_cli,
|
|
"build_account_security_fields",
|
|
return_value={"raw": "RAW", "publicKey": "PUB", "secret": "SEC"},
|
|
),
|
|
patch.object(
|
|
sms_login_cli,
|
|
"login_by_code_try_paths",
|
|
side_effect=(
|
|
(login_sessions[0], [("mobileVerifyCode", login_sessions[0])]),
|
|
(login_sessions[1], [("mobileVerifyCode", login_sessions[1])]),
|
|
),
|
|
) as login,
|
|
patch.object(
|
|
sms_login_cli.sys,
|
|
"argv",
|
|
["sms_login_cli.py", "--mobile", "13800000000", "--code", "123456"],
|
|
),
|
|
redirect_stdout(output),
|
|
):
|
|
self.assertEqual(sms_login_cli.main(), 0)
|
|
self.assertEqual(sms_login_cli.main(), 0)
|
|
|
|
self.assertNotEqual(profiles[0].did, profiles[1].did)
|
|
self.assertEqual(
|
|
bootstrap.call_args_list,
|
|
[
|
|
call(profiles[0], timeout=sms_login_cli.REQUEST_TIMEOUT_SECONDS),
|
|
call(profiles[1], timeout=sms_login_cli.REQUEST_TIMEOUT_SECONDS),
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
generate_passport.call_args_list,
|
|
[call(profiles[0]), call(profiles[1])],
|
|
)
|
|
self.assertIs(login.call_args_list[0].args[0], profiles[0])
|
|
self.assertIs(login.call_args_list[1].args[0], profiles[1])
|
|
text = output.getvalue()
|
|
self.assertIn('ksck="账号#', text)
|
|
self.assertIn(f"did={profiles[0].did}", text)
|
|
self.assertIn("userId=101", text)
|
|
self.assertIn("kuaishou.api_st=API_ST_1", text)
|
|
self.assertIn("token=API_ST_1", text)
|
|
self.assertIn("region_ticket=RT_ACCOUNT_1", text)
|
|
self.assertIn("kuaishou.h5_st=H5_ST_1", text)
|
|
self.assertIn("os=android#SALT_1\"", text)
|
|
self.assertIn("#SALT_1\"", text)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|