feat(auth): print a scannable ASCII QR in the terminal
WSL and --no-browser never showed the PNG, and placeholder Chinese cookies crashed urllib latin-1 headers before the code was issued.
This commit is contained in:
1 parent
12732f6316
commit
2641ba6f87
5 files changed
+183
-11
No files matched your search
+111
-2
@@ -1,16 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""双账号 auth 纯函数与写回隔离测试(不联网): uv run python tests/test_auth.py"""
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from contextlib import redirect_stdout
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
from reaction_board.bilibili.auth import ( # noqa: E402
|
||||
cookie_header,
|
||||
cookie_value,
|
||||
cookies_from_set_cookie,
|
||||
make_headers,
|
||||
@@ -18,7 +21,14 @@ from reaction_board.bilibili.auth import ( # noqa: E402
|
||||
parse_cookie,
|
||||
sessdata_expiry,
|
||||
)
|
||||
from reaction_board.bilibili.auth_flow import AuthService, write_qr_png # noqa: E402
|
||||
from reaction_board.bilibili.auth_flow import ( # noqa: E402
|
||||
AuthService,
|
||||
_QR_BLOCKS,
|
||||
format_qr_ascii,
|
||||
render_qr_ascii,
|
||||
write_qr_png,
|
||||
)
|
||||
import qrcodegen # noqa: E402
|
||||
|
||||
READ_OLD = "buvid3=READ-B3; {{SECRET_i5bghhpt}}readcsrf; DedeUserID=11; SESSDATA=old%2C1700000000%2Ca"
|
||||
WRITE_OLD = "buvid3=WRITE-B3; bili_jct=writecsrf; DedeUserID=22; SESSDATA=old%2C1700000000%2Cb"
|
||||
@@ -79,6 +89,83 @@ def test_qr_png():
|
||||
assert path.read_bytes().startswith(b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
|
||||
def test_render_qr_ascii_roundtrip_and_quiet_zone():
|
||||
text = "https://passport.bilibili.com/h5/cloud/login?qrcode_key=abc"
|
||||
for border in (0, 1, 2, 4):
|
||||
art = render_qr_ascii(text, border=border)
|
||||
qr = qrcodegen.QrCode.encode_text(text, qrcodegen.QrCode.Ecc.MEDIUM)
|
||||
size = qr.get_size()
|
||||
dim = size + border * 2
|
||||
lines = art.split("\n")
|
||||
assert len(lines) == (dim + 1) // 2
|
||||
assert all(len(line) == dim for line in lines)
|
||||
inverse = {glyph: pair for pair, glyph in _QR_BLOCKS.items()}
|
||||
recovered = []
|
||||
for row, line in enumerate(lines):
|
||||
tops, bottoms = [], []
|
||||
for glyph in line:
|
||||
top, bottom = inverse[glyph]
|
||||
tops.append(top)
|
||||
bottoms.append(bottom)
|
||||
recovered.append(tops)
|
||||
if row * 2 + 1 < dim:
|
||||
recovered.append(bottoms)
|
||||
assert len(recovered) == dim
|
||||
for y in range(dim):
|
||||
for x in range(dim):
|
||||
expected = qr.get_module(x - border, y - border)
|
||||
assert recovered[y][x] is expected
|
||||
if border >= 1:
|
||||
assert all(line[0] == " " and line[-1] == " " for line in lines)
|
||||
if border >= 2:
|
||||
assert set(lines[0]) == {" "}
|
||||
|
||||
try:
|
||||
render_qr_ascii(text, border=-1)
|
||||
except ValueError as exc:
|
||||
assert "border" in str(exc)
|
||||
else:
|
||||
raise AssertionError("负 border 应报错")
|
||||
|
||||
colored = format_qr_ascii(text, color=True)
|
||||
plain = format_qr_ascii(text, color=False)
|
||||
assert plain == render_qr_ascii(text)
|
||||
assert "\033[48;5;231m" in colored
|
||||
assert "\033[0m" in colored
|
||||
assert plain.split("\n")[0] in colored
|
||||
|
||||
|
||||
def test_run_prints_ascii_qr_without_network():
|
||||
login_url = "https://passport.bilibili.com/h5/cloud/login?qrcode_key=unit-test"
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "config.json"
|
||||
path.write_text(json.dumps(raw_config(), ensure_ascii=False), encoding="utf-8")
|
||||
service = AuthService(path, account="read")
|
||||
|
||||
def fake_request(credentials, url, *, auth):
|
||||
if "qrcode/generate" in url:
|
||||
return {
|
||||
"code": 0,
|
||||
"message": "OK",
|
||||
"data": {"url": login_url, "qrcode_key": "abcdefghijklmnop"},
|
||||
}, []
|
||||
if "qrcode/poll" in url:
|
||||
return {"code": 0, "data": {"code": 86038, "message": "二维码已过期"}}, []
|
||||
raise AssertionError(url)
|
||||
|
||||
service._request = fake_request # type: ignore[method-assign]
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
assert service.run(timeout=9, no_browser=True) == 3
|
||||
output = buf.getvalue()
|
||||
ascii_qr = render_qr_ascii(login_url)
|
||||
assert ascii_qr in output
|
||||
assert "请使用哔哩哔哩 App 扫描下方二维码" in output
|
||||
assert login_url in output
|
||||
assert "二维码已过期" in output
|
||||
assert "bili-fav-read-abcdefgh.png" in output
|
||||
|
||||
|
||||
def test_make_headers_matches_raw_request():
|
||||
headers = make_headers({"cookie": "SESSDATA=x; buvid3=y"}, auth=False)
|
||||
assert headers["origin"] == "https://space.bilibili.com"
|
||||
@@ -96,14 +183,36 @@ def test_make_headers_matches_raw_request():
|
||||
{"cookie": ""}, referer="https://space.bilibili.com/1/favlist?fid=2"
|
||||
)
|
||||
assert custom["referer"] == "https://space.bilibili.com/1/favlist?fid=2"
|
||||
assert custom["cookie"] == ""
|
||||
|
||||
|
||||
def test_make_headers_drops_placeholder_and_non_latin1_cookies():
|
||||
placeholder = "只读账号的完整 Cookie(需含 SESSDATA、bili_jct、DedeUserID 等)"
|
||||
headers = make_headers({"cookie": placeholder}, auth=False)
|
||||
for value in headers.values():
|
||||
value.encode("latin-1")
|
||||
assert headers["cookie"] == ""
|
||||
assert cookie_header(placeholder, auth=True) == ""
|
||||
|
||||
mixed = make_headers(
|
||||
{"cookie": "buvid3=abc; 写入账号的完整 Cookie; SESSDATA=x; note=中文"},
|
||||
auth=True,
|
||||
)
|
||||
mixed["cookie"].encode("latin-1")
|
||||
assert mixed["cookie"] == "buvid3=abc; SESSDATA=x"
|
||||
missing = make_headers({}, auth=False)
|
||||
assert missing["cookie"] == ""
|
||||
|
||||
|
||||
def main():
|
||||
test_cookie_helpers()
|
||||
test_finish_updates_only_selected_account_and_rolls_back()
|
||||
test_qr_png()
|
||||
test_render_qr_ascii_roundtrip_and_quiet_zone()
|
||||
test_run_prints_ascii_qr_without_network()
|
||||
test_make_headers_matches_raw_request()
|
||||
print("OK: 双账号 auth Cookie 合并、写回隔离与回滚测试全部通过")
|
||||
test_make_headers_drops_placeholder_and_non_latin1_cookies()
|
||||
print("OK: 双账号 auth Cookie 合并、写回隔离、终端二维码与回滚测试全部通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user