11import io
22import json
3+ import tempfile
34import unittest
5+ from pathlib import Path
46from unittest .mock import MagicMock , patch
57
8+ import requests
9+
610from codecarbon .cli import auth
711from codecarbon .cli .auth import _CallbackHandler
812
@@ -100,6 +104,31 @@ def test_validate_access_token_valid(
100104 ):
101105 self .assertTrue (auth ._validate_access_token ("token" ))
102106
107+ @patch ("codecarbon.cli.auth._discover_endpoints" , return_value = {"jwks_uri" : "jwks" })
108+ @patch (
109+ "codecarbon.cli.auth.requests.get" ,
110+ side_effect = requests .RequestException ("offline" ),
111+ )
112+ def test_validate_access_token_network_error_returns_true (
113+ self , mock_get , mock_discover
114+ ):
115+ self .assertTrue (auth ._validate_access_token ("token" ))
116+
117+ @patch ("codecarbon.cli.auth._discover_endpoints" , return_value = {"jwks_uri" : "jwks" })
118+ @patch ("codecarbon.cli.auth.requests.get" )
119+ @patch ("codecarbon.cli.auth.JsonWebKey.import_key_set" )
120+ @patch (
121+ "codecarbon.cli.auth.jose_jwt.decode" ,
122+ side_effect = Exception ("invalid" ),
123+ )
124+ def test_validate_access_token_invalid_returns_false (
125+ self , mock_decode , mock_import_key_set , mock_get , mock_discover
126+ ):
127+ mock_get .return_value .json .return_value = {"keys" : []}
128+ mock_get .return_value .raise_for_status .return_value = None
129+
130+ self .assertFalse (auth ._validate_access_token ("token" ))
131+
103132 @patch ("codecarbon.cli.auth.requests.post" )
104133 @patch ("codecarbon.cli.auth._discover_endpoints" )
105134 def test_refresh_tokens (self , mock_discover , mock_post ):
@@ -120,6 +149,18 @@ def test_get_access_token_valid(self, mock_validate, mock_load):
120149 mock_validate .return_value = True
121150 self .assertEqual (auth .get_access_token (), "a" )
122151
152+ @patch ("codecarbon.cli.auth._load_credentials" , side_effect = OSError ("missing" ))
153+ def test_get_access_token_raises_when_credentials_missing (self , mock_load ):
154+ with self .assertRaises (ValueError ):
155+ auth .get_access_token ()
156+
157+ @patch ("codecarbon.cli.auth._load_credentials" )
158+ def test_get_access_token_raises_when_access_token_missing (self , mock_load ):
159+ mock_load .return_value = {"refresh_token" : "r" }
160+
161+ with self .assertRaises (ValueError ):
162+ auth .get_access_token ()
163+
123164 @patch ("codecarbon.cli.auth._load_credentials" )
124165 @patch ("codecarbon.cli.auth._validate_access_token" )
125166 @patch ("codecarbon.cli.auth._refresh_tokens" )
@@ -133,11 +174,138 @@ def test_get_access_token_refresh(
133174 self .assertEqual (auth .get_access_token (), "b" )
134175 mock_save .assert_called ()
135176
177+ @patch ("codecarbon.cli.auth._refresh_tokens" , side_effect = Exception ("expired" ))
178+ @patch ("codecarbon.cli.auth._validate_access_token" , return_value = False )
179+ @patch (
180+ "codecarbon.cli.auth._load_credentials" ,
181+ return_value = {"access_token" : "a" , "refresh_token" : "r" },
182+ )
183+ def test_get_access_token_refresh_failure_deletes_credentials (
184+ self , mock_load , mock_validate , mock_refresh
185+ ):
186+ original_credentials_file = auth ._CREDENTIALS_FILE
187+ with tempfile .TemporaryDirectory () as tmp_dir :
188+ temp_credentials = Path (tmp_dir ) / "test_credentials.json"
189+ temp_credentials .write_text ("{}" )
190+ try :
191+ auth ._CREDENTIALS_FILE = temp_credentials
192+ with self .assertRaises (ValueError ):
193+ auth .get_access_token ()
194+ self .assertFalse (temp_credentials .exists ())
195+ finally :
196+ auth ._CREDENTIALS_FILE = original_credentials_file
197+
136198 @patch ("codecarbon.cli.auth._load_credentials" )
137199 def test_get_id_token (self , mock_load ):
138200 mock_load .return_value = {"id_token" : "i" }
139201 self .assertEqual (auth .get_id_token (), "i" )
140202
203+ @patch ("codecarbon.cli.auth._save_credentials" )
204+ @patch ("codecarbon.cli.auth.webbrowser.open" )
205+ @patch ("codecarbon.cli.auth.HTTPServer" )
206+ @patch ("codecarbon.cli.auth.OAuth2Session" )
207+ @patch (
208+ "codecarbon.cli.auth._discover_endpoints" ,
209+ return_value = {
210+ "authorization_endpoint" : "https://auth.example/authorize" ,
211+ "token_endpoint" : "https://auth.example/token" ,
212+ },
213+ )
214+ def test_authorize_success (
215+ self ,
216+ mock_discover ,
217+ mock_session_cls ,
218+ mock_server_cls ,
219+ mock_browser_open ,
220+ mock_save_credentials ,
221+ ):
222+ mock_session = MagicMock ()
223+ mock_session .create_authorization_url .return_value = (
224+ "https://auth.example/authorize?state=abc" ,
225+ "abc" ,
226+ )
227+ mock_session .fetch_token .return_value = {"access_token" : "token" }
228+ mock_session_cls .return_value = mock_session
229+
230+ mock_server = MagicMock ()
231+ mock_server .handle_request .side_effect = lambda : setattr (
232+ auth ._CallbackHandler ,
233+ "callback_url" ,
234+ "http://localhost:8090/callback?code=123" ,
235+ )
236+ mock_server_cls .return_value = mock_server
237+
238+ auth ._CallbackHandler .callback_url = None
239+ auth ._CallbackHandler .error = None
240+
241+ result = auth .authorize ()
242+
243+ self .assertEqual (result , {"access_token" : "token" })
244+ mock_browser_open .assert_called_once ()
245+ mock_server .handle_request .assert_called_once ()
246+ mock_server .server_close .assert_called_once ()
247+ mock_save_credentials .assert_called_once_with ({"access_token" : "token" })
248+
249+ @patch ("codecarbon.cli.auth.HTTPServer" )
250+ @patch ("codecarbon.cli.auth.OAuth2Session" )
251+ @patch (
252+ "codecarbon.cli.auth._discover_endpoints" ,
253+ return_value = {
254+ "authorization_endpoint" : "https://auth.example/authorize" ,
255+ "token_endpoint" : "https://auth.example/token" ,
256+ },
257+ )
258+ def test_authorize_raises_on_callback_error (
259+ self , mock_discover , mock_session_cls , mock_server_cls
260+ ):
261+ mock_session = MagicMock ()
262+ mock_session .create_authorization_url .return_value = (
263+ "https://auth.example/authorize?state=abc" ,
264+ "abc" ,
265+ )
266+ mock_session_cls .return_value = mock_session
267+ mock_server = MagicMock ()
268+ mock_server .handle_request .side_effect = lambda : setattr (
269+ auth ._CallbackHandler ,
270+ "error" ,
271+ "access_denied" ,
272+ )
273+ mock_server_cls .return_value = mock_server
274+
275+ auth ._CallbackHandler .callback_url = None
276+ auth ._CallbackHandler .error = None
277+
278+ with self .assertRaises (ValueError ):
279+ auth .authorize ()
280+ mock_server .handle_request .assert_called_once ()
281+ mock_server .server_close .assert_called_once ()
282+
283+ @patch ("codecarbon.cli.auth.HTTPServer" )
284+ @patch ("codecarbon.cli.auth.OAuth2Session" )
285+ @patch (
286+ "codecarbon.cli.auth._discover_endpoints" ,
287+ return_value = {
288+ "authorization_endpoint" : "https://auth.example/authorize" ,
289+ "token_endpoint" : "https://auth.example/token" ,
290+ },
291+ )
292+ def test_authorize_raises_when_no_callback_received (
293+ self , mock_discover , mock_session_cls , mock_server_cls
294+ ):
295+ mock_session = MagicMock ()
296+ mock_session .create_authorization_url .return_value = (
297+ "https://auth.example/authorize?state=abc" ,
298+ "abc" ,
299+ )
300+ mock_session_cls .return_value = mock_session
301+ mock_server_cls .return_value = MagicMock ()
302+
303+ auth ._CallbackHandler .callback_url = None
304+ auth ._CallbackHandler .error = None
305+
306+ with self .assertRaises (ValueError ):
307+ auth .authorize ()
308+
141309
142310if __name__ == "__main__" :
143311 unittest .main ()
0 commit comments