@@ -680,6 +680,17 @@ describe("loginGeminiCliOAuth", () => {
680680 } ) ;
681681 }
682682
683+ function responseTextBodyWithTextTrap ( body : string , status = 500 ) {
684+ const response = new Response ( body , {
685+ status,
686+ headers : { "Content-Type" : "text/plain" } ,
687+ } ) ;
688+ const text = vi
689+ . spyOn ( response , "text" )
690+ . mockRejectedValue ( new Error ( "unexpected response.text() call" ) ) ;
691+ return { response, text } ;
692+ }
693+
683694 function tokenResponse ( ) : Response {
684695 return responseJson ( {
685696 access_token : "access-token" ,
@@ -927,6 +938,30 @@ describe("loginGeminiCliOAuth", () => {
927938 expect ( requests . filter ( ( { url } ) => url . includes ( "v1internal:loadCodeAssist" ) ) ) . toHaveLength ( 3 ) ;
928939 } ) ;
929940
941+ it . each ( [
942+ [
943+ "exchange" ,
944+ "x" ,
945+ async ( ) =>
946+ ( await import ( "./oauth.token.js" ) ) . exchangeCodeForTokens ( "oauth-code" , "pkce-verifier" ) ,
947+ ] ,
948+ [
949+ "refresh" ,
950+ "y" ,
951+ async ( ) =>
952+ ( await import ( "./oauth.token.js" ) ) . refreshTokensForGeminiCli ( { refresh : "refresh-token" } ) ,
953+ ] ,
954+ ] ) ( "bounds token %s error bodies without using response.text()" , async ( _flow , fill , request ) => {
955+ const { response, text } = responseTextBodyWithTextTrap ( fill . repeat ( 32 * 1024 ) , 500 ) ;
956+ installGeminiOAuthFetchMock ( ( ) => undefined , { tokenResponse : ( ) => response } ) ;
957+
958+ const error = await request ( ) . catch ( ( err : unknown ) => err ) ;
959+
960+ expect ( error ) . toBeInstanceOf ( Error ) ;
961+ expect ( ( error as Error ) . message ) . toBe ( `Token exchange failed: ${ fill . repeat ( 8 * 1024 ) } ` ) ;
962+ expect ( text ) . not . toHaveBeenCalled ( ) ;
963+ } ) ;
964+
930965 it ( "falls back to GOOGLE_CLOUD_PROJECT when all loadCodeAssist endpoints fail" , async ( ) => {
931966 process . env . GOOGLE_CLOUD_PROJECT = "env-project" ;
932967
0 commit comments