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()