157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
import logging
|
|
import unittest
|
|
import urllib.error
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
from app.bilibili_cookie_refresh import BilibiliQrLogin, _request_json
|
|
|
|
|
|
class _JsonResponse:
|
|
def __init__(self, payload=b'{"code": 0}'):
|
|
self.payload = payload
|
|
|
|
def read(self):
|
|
return self.payload
|
|
|
|
|
|
class BilibiliRequestTests(unittest.TestCase):
|
|
def test_get_retries_transient_connection_failures(self):
|
|
opener = Mock()
|
|
response = _JsonResponse()
|
|
opener.open.side_effect = [
|
|
urllib.error.URLError(ConnectionRefusedError(10061, "connection refused")),
|
|
urllib.error.URLError(ConnectionRefusedError(10061, "connection refused")),
|
|
response,
|
|
]
|
|
|
|
with patch("app.bilibili_cookie_refresh.time.sleep") as sleep:
|
|
payload, actual_response = _request_json(
|
|
"https://passport.bilibili.com/test",
|
|
opener=opener,
|
|
retries=2,
|
|
)
|
|
|
|
self.assertEqual(payload, {"code": 0})
|
|
self.assertIs(actual_response, response)
|
|
self.assertEqual(opener.open.call_count, 3)
|
|
self.assertEqual([call.args[0] for call in sleep.call_args_list], [0.4, 0.8])
|
|
|
|
def test_exhausted_retries_hide_low_level_network_error(self):
|
|
opener = Mock()
|
|
opener.open.side_effect = urllib.error.URLError(
|
|
ConnectionRefusedError(10061, "connection refused")
|
|
)
|
|
|
|
with patch("app.bilibili_cookie_refresh.time.sleep"), self.assertRaisesRegex(
|
|
RuntimeError,
|
|
"^连接B站登录服务失败,请稍后重试$",
|
|
) as raised:
|
|
_request_json(
|
|
"https://passport.bilibili.com/test",
|
|
opener=opener,
|
|
retries=2,
|
|
)
|
|
|
|
self.assertEqual(opener.open.call_count, 3)
|
|
self.assertNotIn("10061", str(raised.exception))
|
|
|
|
|
|
class _CredentialStore:
|
|
def __init__(self):
|
|
self.refresh_token = ""
|
|
|
|
def save_refresh_token(self, refresh_token):
|
|
self.refresh_token = refresh_token
|
|
|
|
def is_configured(self):
|
|
return bool(self.refresh_token)
|
|
|
|
|
|
class BilibiliQrLoginTests(unittest.IsolatedAsyncioTestCase):
|
|
def setUp(self):
|
|
self.store = _CredentialStore()
|
|
self.updated_cookies = []
|
|
self.on_logged_in = AsyncMock()
|
|
self.login = BilibiliQrLogin(
|
|
credential_store=self.store,
|
|
update_cookie=self.updated_cookies.append,
|
|
logger=logging.getLogger("test-bilibili-qr"),
|
|
on_logged_in=self.on_logged_in,
|
|
)
|
|
|
|
@staticmethod
|
|
def _generate_response():
|
|
return ({
|
|
"code": 0,
|
|
"data": {
|
|
"url": "https://passport.bilibili.com/h5-app/passport/login/scan?navhide=1",
|
|
"qrcode_key": "private-qrcode-key",
|
|
},
|
|
}, None)
|
|
|
|
async def test_start_keeps_qrcode_key_on_server(self):
|
|
with patch("app.bilibili_cookie_refresh._request_json", return_value=self._generate_response()):
|
|
result = await self.login.start()
|
|
|
|
self.assertEqual(result["state"], "awaiting_scan")
|
|
self.assertTrue(result["has_qr_image"])
|
|
self.assertNotIn("qr_key", result)
|
|
self.assertNotIn("qr_url", result)
|
|
self.assertFalse(result["credential_configured"])
|
|
|
|
async def test_poll_reports_scanned_without_saving_credentials(self):
|
|
with patch("app.bilibili_cookie_refresh._request_json", return_value=self._generate_response()):
|
|
await self.login.start()
|
|
with patch("app.bilibili_cookie_refresh._request_json", return_value=({
|
|
"code": 0,
|
|
"data": {"code": 86090, "message": "二维码已扫码未确认"},
|
|
}, None)):
|
|
result = await self.login.poll()
|
|
|
|
self.assertEqual(result["state"], "awaiting_confirm")
|
|
self.assertEqual(self.store.refresh_token, "")
|
|
self.assertEqual(self.updated_cookies, [])
|
|
self.on_logged_in.assert_not_awaited()
|
|
|
|
async def test_success_saves_refresh_token_and_updates_cookie(self):
|
|
with patch("app.bilibili_cookie_refresh._request_json", return_value=self._generate_response()):
|
|
await self.login.start()
|
|
|
|
login_url = (
|
|
"https://www.bilibili.com/?SESSDATA=session-value&bili_jct=csrf-value"
|
|
"&DedeUserID=123456"
|
|
)
|
|
|
|
def request_side_effect(url, **_kwargs):
|
|
if "qrcode/poll" in url:
|
|
return ({
|
|
"code": 0,
|
|
"data": {
|
|
"code": 0,
|
|
"url": login_url,
|
|
"refresh_token": "refresh-token-value",
|
|
},
|
|
}, None)
|
|
if "x/web-interface/nav" in url:
|
|
return ({
|
|
"code": 0,
|
|
"data": {"isLogin": True, "mid": 123456, "uname": "测试账号"},
|
|
}, None)
|
|
raise AssertionError(f"unexpected url: {url}")
|
|
|
|
with patch("app.bilibili_cookie_refresh._request_json", side_effect=request_side_effect):
|
|
result = await self.login.poll()
|
|
|
|
self.assertEqual(result["state"], "completed")
|
|
self.assertEqual(result["account"]["uname"], "测试账号")
|
|
self.assertTrue(result["credential_configured"])
|
|
self.assertNotIn("refresh_token", result)
|
|
self.assertEqual(self.store.refresh_token, "refresh-token-value")
|
|
self.assertEqual(self.updated_cookies[0]["SESSDATA"], "session-value")
|
|
self.assertEqual(self.updated_cookies[0]["bili_jct"], "csrf-value")
|
|
self.on_logged_in.assert_awaited_once()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|