@@ -226,10 +226,23 @@ pub async fn get_gh_user(
226226 let gh = account
227227 . client ( & access_token)
228228 . context ( "Failed to create GitHub client" ) ?;
229- let user = gh
230- . get_authenticated ( )
231- . await
232- . context ( "Failed to get authenticated user" ) ?;
229+ let user = match gh. get_authenticated ( ) . await {
230+ Ok ( user) => user,
231+ Err ( client_err) => {
232+ // Check if this is a network error before converting to anyhow
233+ if is_network_error ( & client_err) {
234+ return Err ( anyhow:: Error :: from ( client_err) . context (
235+ but_error:: Context :: new_static (
236+ but_error:: Code :: NetworkError ,
237+ "Unable to connect to GitHub." ,
238+ ) ,
239+ ) ) ;
240+ }
241+ return Err (
242+ anyhow:: Error :: from ( client_err) . context ( "Failed to get authenticated user" )
243+ ) ;
244+ }
245+ } ;
233246 Ok ( Some ( AuthenticatedUser {
234247 access_token,
235248 login : user. login ,
@@ -242,6 +255,14 @@ pub async fn get_gh_user(
242255 }
243256}
244257
258+ /// Check if an error is a network connectivity error.
259+ ///
260+ /// This includes DNS resolution failures, connection timeouts, connection refused, etc.
261+ fn is_network_error ( err : & octorust:: ClientError ) -> bool {
262+ matches ! ( err, octorust:: ClientError :: ReqwestError ( reqwest_err)
263+ if reqwest_err. is_timeout( ) || reqwest_err. is_connect( ) || reqwest_err. is_request( ) )
264+ }
265+
245266#[ derive( Debug , Clone , PartialEq , Eq ) ]
246267pub enum CredentialCheckResult {
247268 Valid ,
@@ -290,3 +311,75 @@ pub async fn list_known_github_accounts(
290311pub fn clear_all_github_tokens ( storage : & but_forge_storage:: controller:: Controller ) -> Result < ( ) > {
291312 token:: clear_all_github_accounts ( storage) . context ( "Failed to clear all GitHub tokens" )
292313}
314+
315+ #[ cfg( test) ]
316+ mod tests {
317+ use super :: * ;
318+
319+ #[ test]
320+ fn test_is_network_error_with_reqwest_timeout ( ) {
321+ // Create a reqwest error by making an actual HTTP request that will timeout
322+ let client = reqwest:: blocking:: Client :: builder ( )
323+ . timeout ( std:: time:: Duration :: from_millis ( 1 ) )
324+ . build ( )
325+ . unwrap ( ) ;
326+
327+ // Try to connect to a non-routable IP address (should timeout)
328+ let result = client. get ( "http://192.0.2.1:80" ) . send ( ) ;
329+
330+ if let Err ( reqwest_err) = result {
331+ let client_err = octorust:: ClientError :: ReqwestError ( reqwest_err) ;
332+ assert ! (
333+ is_network_error( & client_err) ,
334+ "Should detect timeout/connection errors"
335+ ) ;
336+ } else {
337+ panic ! ( "Expected a network error but request succeeded" ) ;
338+ }
339+ }
340+
341+ #[ test]
342+ fn test_is_network_error_with_connection_error ( ) {
343+ // Create a reqwest error and wrap it in ClientError
344+ let client = reqwest:: blocking:: Client :: builder ( )
345+ . timeout ( std:: time:: Duration :: from_millis ( 1 ) )
346+ . build ( )
347+ . unwrap ( ) ;
348+
349+ let result = client. get ( "http://192.0.2.1:80" ) . send ( ) ;
350+
351+ if let Err ( reqwest_err) = result {
352+ let client_err = octorust:: ClientError :: ReqwestError ( reqwest_err) ;
353+ assert ! (
354+ is_network_error( & client_err) ,
355+ "Should detect ClientError wrapping reqwest network errors"
356+ ) ;
357+ } else {
358+ panic ! ( "Expected a network error but request succeeded" ) ;
359+ }
360+ }
361+
362+ #[ test]
363+ fn test_is_not_network_error_http_error ( ) {
364+ // HTTP errors (like 401) are not network errors
365+ let client_err = octorust:: ClientError :: HttpError {
366+ status : http:: StatusCode :: UNAUTHORIZED ,
367+ headers : reqwest:: header:: HeaderMap :: new ( ) ,
368+ error : "Unauthorized" . to_string ( ) ,
369+ } ;
370+ assert ! (
371+ !is_network_error( & client_err) ,
372+ "Should not detect HTTP status errors as network errors"
373+ ) ;
374+ }
375+
376+ #[ test]
377+ fn test_is_not_network_error_rate_limit ( ) {
378+ // Rate limit errors are not network errors
379+ let client_err = octorust:: ClientError :: RateLimited { duration : 60 } ;
380+ assert ! (
381+ !is_network_error( & client_err) ,
382+ "Should not detect rate limit errors as network errors"
383+ ) ;
384+ }
385+ }
0 commit comments