11import unittest
22
3+ from requests import ConnectionError
4+
35from src .tchmaterial_parser .ui import download_panel , runtime
46
57
68class FakeResponse :
7- def __init__ (self , status_code : int ) -> None :
8- self .ok = False
9+ def __init__ (self , status_code : int , content : bytes = b"" ) -> None :
10+ self .ok = status_code < 400
911 self .status_code = status_code
12+ self .content = content
13+
14+ def close (self ) -> None :
15+ pass
1016
1117
1218class FakeSession :
13- def __init__ (self , status_code : int ) -> None :
14- self .status_code = status_code
19+ def __init__ (self , status_code : int | list [int ]) -> None :
20+ self .status_codes = status_code if isinstance (status_code , list ) else [status_code ]
21+ self .requested_urls : list [str ] = []
1522
1623 def get (self , * args : tuple , ** kwargs : dict ) -> FakeResponse :
17- return FakeResponse (self .status_code )
24+ self .requested_urls .append (args [0 ])
25+ status_code = self .status_codes [min (len (self .requested_urls ) - 1 , len (self .status_codes ) - 1 )]
26+ return FakeResponse (status_code )
1827
1928
2029class FakeWidget :
2130 def config (self , ** kwargs : dict ) -> None :
2231 pass
2332
2433
34+ class FailingSession :
35+ def get (self , url : str , ** kwargs : dict ) -> FakeResponse :
36+ raise ConnectionError (f"无法访问 { url } " )
37+
38+
2539class DownloadFailureTest (unittest .TestCase ):
2640 def setUp (self ) -> None :
2741 # 置为关闭状态后 ui_call() 不会真正执行回调,因此桩控件只需提供 config 属性
2842 runtime .app_closing = True
2943 self .addCleanup (setattr , runtime , "app_closing" , False )
3044 self .addCleanup (setattr , download_panel , "session" , download_panel .session )
45+ previous_token = download_panel .config .access_token
46+ self .addCleanup (setattr , download_panel .config , "access_token" , previous_token )
3147 widget = FakeWidget ()
3248 download_panel .bind_widgets (widget , widget , widget , widget , widget )
3349
@@ -41,13 +57,104 @@ def test_reports_server_errors_unrelated_to_the_token(self) -> None:
4157 self .assertEqual (self .failure_reason (404 ), "服务器返回 HTTP 状态码 404" )
4258
4359 def test_appends_a_token_hint_to_authentication_failures (self ) -> None :
60+ download_panel .config .access_token = "private-token"
4461 for status_code in (401 , 403 ):
4562 with self .subTest (status_code = status_code ):
4663 self .assertEqual (
4764 self .failure_reason (status_code ),
4865 f"服务器返回 HTTP 状态码 { status_code } ,Access Token 可能已过期或无效,请重新设置" ,
4966 )
5067
68+ def test_asks_for_token_when_anonymous_request_requires_authentication (self ) -> None :
69+ download_panel .config .access_token = None
70+ self .assertEqual (
71+ self .failure_reason (401 ),
72+ "服务器返回 HTTP 状态码 401,该资源需要有效的 Access Token,请先设置" ,
73+ )
74+
75+ def test_adds_access_token_only_to_private_request_url (self ) -> None :
76+ token = "private-token"
77+ download_panel .config .access_token = token
78+ fake_session = FakeSession (404 )
79+ download_panel .session = fake_session
80+ original_url = "https://r1-ndr-private.ykt.cbern.com.cn/book.pdf?source=catalog"
81+
82+ download_panel .download_states = []
83+ download_panel .download_file (original_url , "book.pdf" )
84+
85+ requested_url = fake_session .requested_urls [0 ]
86+ self .assertIn ("source=catalog" , requested_url )
87+ self .assertIn ("accessToken=private-token" , requested_url )
88+ self .assertEqual (download_panel .download_states [0 ]["download_url" ], original_url )
89+ self .assertNotIn (token , download_panel .download_states [0 ]["failed_reason" ])
90+
91+ def test_keeps_anonymous_and_non_private_urls_unchanged (self ) -> None :
92+ private_url = "https://r1-ndr-private.ykt.cbern.com.cn/book.pdf"
93+ public_url = "https://example.com/book.pdf"
94+
95+ download_panel .config .access_token = None
96+ self .assertEqual (download_panel .authenticated_download_url (private_url ), private_url )
97+
98+ download_panel .config .access_token = "private-token"
99+ self .assertEqual (download_panel .authenticated_download_url (public_url ), public_url )
100+
101+ def test_retries_private_download_on_the_next_mirror (self ) -> None :
102+ download_panel .config .access_token = "private-token"
103+ fake_session = FakeSession ([500 , 200 ])
104+ download_panel .session = fake_session
105+ original_url = "https://r1-ndr-private.ykt.cbern.com.cn/book.pdf"
106+
107+ response , attempted_urls = download_panel .request_download (original_url )
108+
109+ self .assertEqual (response .status_code , 200 )
110+ self .assertEqual ([url .split ("/" , 3 )[2 ] for url in attempted_urls ], [
111+ "r1-ndr-private.ykt.cbern.com.cn" ,
112+ "r2-ndr-private.ykt.cbern.com.cn" ,
113+ ])
114+ self .assertTrue (all ("accessToken=private-token" in url for url in fake_session .requested_urls ))
115+ self .assertTrue (all ("accessToken" not in url for url in attempted_urls ))
116+
117+ def test_does_not_retry_authentication_failures_or_public_urls (self ) -> None :
118+ private_url = "https://r1-ndr-private.ykt.cbern.com.cn/book.pdf"
119+ public_url = "https://example.com/book.pdf"
120+
121+ private_session = FakeSession (401 )
122+ download_panel .session = private_session
123+ _response , private_attempts = download_panel .request_download (private_url )
124+ self .assertEqual (private_attempts , [private_url ])
125+
126+ public_session = FakeSession (500 )
127+ download_panel .session = public_session
128+ _response , public_attempts = download_panel .request_download (public_url )
129+ self .assertEqual (public_attempts , [public_url ])
130+
131+ def test_explains_private_storage_authentication_errors (self ) -> None :
132+ response = FakeResponse (400 , b"<Error><Code>InvalidArgument</Code></Error>" )
133+ attempted_urls = ["https://r1.example/book.pdf" , "https://r2.example/book.pdf" ]
134+
135+ download_panel .config .access_token = None
136+ self .assertEqual (
137+ download_panel .download_failure_reason (response , attempted_urls ),
138+ "服务器返回 HTTP 状态码 400(InvalidArgument),该私有资源需要有效的 Access Token,请先设置,已尝试 2 个下载镜像" ,
139+ )
140+
141+ download_panel .config .access_token = "private-token"
142+ self .assertEqual (
143+ download_panel .download_failure_reason (response , attempted_urls ),
144+ "服务器返回 HTTP 状态码 400(InvalidArgument),私有资源鉴权失败,Access Token 可能已过期或无效,请重新设置,已尝试 2 个下载镜像" ,
145+ )
146+
147+ def test_redacts_token_from_network_exceptions (self ) -> None :
148+ token = "private-token"
149+ download_panel .config .access_token = token
150+ download_panel .session = FailingSession ()
151+
152+ with self .assertRaises (RuntimeError ) as context :
153+ download_panel .request_download ("https://r1-ndr-private.ykt.cbern.com.cn/book.pdf" )
154+
155+ self .assertNotIn (token , str (context .exception ))
156+ self .assertIn ("accessToken=<已隐藏>" , str (context .exception ))
157+
51158
52159if __name__ == "__main__" :
53160 unittest .main ()
0 commit comments