@@ -149,6 +149,72 @@ def fail_request(_request, timeout):
149149 }
150150
151151
152+ def test_http_json_preserves_oauth_device_error_reason (load_script , monkeypatch ):
153+ api = load_script ("youtube-autoencoder-api" , "yta_api_oauth_error" )
154+ body = json .dumps (
155+ {
156+ "error" : "authorization_pending" ,
157+ "error_description" : "The user has not completed authorization." ,
158+ }
159+ ).encode ()
160+
161+ def fail_request (_request , timeout ):
162+ assert timeout == 30
163+ raise urllib .error .HTTPError (
164+ "https://oauth2.googleapis.com/token" ,
165+ 400 ,
166+ "Bad Request" ,
167+ {},
168+ io .BytesIO (body ),
169+ )
170+
171+ monkeypatch .setattr (api .urllib .request , "urlopen" , fail_request )
172+
173+ with pytest .raises (api .YouTubeApiError ) as raised :
174+ api .http_json ("POST" , "https://oauth2.googleapis.com/token" , form = {"device_code" : "code" })
175+
176+ assert raised .value .reasons == ("authorization_pending" ,)
177+ assert str (raised .value ) == "The user has not completed authorization."
178+
179+
180+ def test_authorize_retries_pending_device_flow (load_script , monkeypatch , tmp_path ):
181+ api = load_script ("youtube-autoencoder-api" , "yta_api_authorize_pending" )
182+ calls = []
183+ responses = iter (
184+ [
185+ {
186+ "verification_url" : "https://example.test/device" ,
187+ "user_code" : "ABCD-EFGH" ,
188+ "device_code" : "device-code" ,
189+ "interval" : 1 ,
190+ "expires_in" : 600 ,
191+ },
192+ api .YouTubeApiError (
193+ status = 400 ,
194+ reasons = ("authorization_pending" ,),
195+ message = "authorization_pending" ,
196+ ),
197+ {"access_token" : "access" , "refresh_token" : "refresh" , "expires_in" : 3600 },
198+ ]
199+ )
200+
201+ def fake_http (* _args , ** _kwargs ):
202+ calls .append (True )
203+ response = next (responses )
204+ if isinstance (response , Exception ):
205+ raise response
206+ return response
207+
208+ monkeypatch .setattr (api , "TOKEN_FILE" , tmp_path / "youtube-token.json" )
209+ monkeypatch .setattr (api , "client_config" , lambda : {"client_id" : "id" , "client_secret" : "secret" })
210+ monkeypatch .setattr (api , "http_json" , fake_http )
211+ monkeypatch .setattr (api .time , "sleep" , lambda _seconds : None )
212+
213+ assert api .authorize (argparse .Namespace ()) == 0
214+ assert len (calls ) == 3
215+ assert json .loads (api .TOKEN_FILE .read_text (encoding = "utf-8" ))["refresh_token" ] == "refresh"
216+
217+
152218def test_corrupt_state_is_quarantined (load_script , monkeypatch , tmp_path ):
153219 api = load_script ("youtube-autoencoder-api" , "yta_api_corrupt_state" )
154220 state = tmp_path / "youtube-live-state.json"
0 commit comments