@@ -1133,11 +1133,40 @@ describe("google transport stream", () => {
11331133
11341134 expect ( googleAuthMock ) . toHaveBeenCalledWith ( {
11351135 scopes : [ "https://www.googleapis.com/auth/cloud-platform" ] ,
1136+ clientOptions : { transporterOptions : { timeout : 30_000 } } ,
11361137 } ) ;
11371138 expect ( googleAuthGetAccessTokenMock ) . toHaveBeenCalledTimes ( 1 ) ;
11381139 expect ( tokenFetchMock ) . not . toHaveBeenCalled ( ) ;
11391140 } ) ;
11401141
1142+ it ( "bounds google-auth-library ADC token resolution at the Vertex owner" , async ( ) => {
1143+ const tempDir = await mkdtemp (
1144+ path . join ( os . tmpdir ( ) , "openclaw-google-vertex-authlib-timeout-" ) ,
1145+ ) ;
1146+ vi . stubEnv ( "GOOGLE_APPLICATION_CREDENTIALS" , "" ) ;
1147+ vi . stubEnv ( "HOME" , path . join ( tempDir , "home" ) ) ;
1148+ vi . stubEnv ( "APPDATA" , "" ) ;
1149+ vi . useFakeTimers ( ) ;
1150+ googleAuthGetAccessTokenMock
1151+ . mockReturnValueOnce ( new Promise ( ( ) => { } ) )
1152+ . mockResolvedValueOnce ( "ya29.recovered-token" ) ;
1153+
1154+ const pendingRefresh = resolveGoogleVertexAuthorizedUserHeaders ( vi . fn ( ) ) ;
1155+ const refreshError = pendingRefresh . catch ( ( error : unknown ) => error ) ;
1156+ await vi . waitFor ( ( ) => expect ( googleAuthGetAccessTokenMock ) . toHaveBeenCalledOnce ( ) ) ;
1157+ await vi . advanceTimersByTimeAsync ( 30_000 ) ;
1158+
1159+ await expect ( refreshError ) . resolves . toMatchObject ( {
1160+ name : "TimeoutError" ,
1161+ message : "request timed out" ,
1162+ } ) ;
1163+ await expect ( resolveGoogleVertexAuthorizedUserHeaders ( vi . fn ( ) ) ) . resolves . toEqual ( {
1164+ Authorization : "Bearer ya29.recovered-token" ,
1165+ } ) ;
1166+ expect ( googleAuthMock ) . toHaveBeenCalledTimes ( 2 ) ;
1167+ expect ( googleAuthGetAccessTokenMock ) . toHaveBeenCalledTimes ( 2 ) ;
1168+ } ) ;
1169+
11411170 it ( "does not cache google-auth ADC tokens when fallback expiry would exceed Date range" , async ( ) => {
11421171 const tempDir = await mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-google-vertex-authlib-expiry-" ) ) ;
11431172 vi . useFakeTimers ( ) ;
@@ -1314,6 +1343,56 @@ describe("google transport stream", () => {
13141343 expect ( result . content ) . toEqual ( [ { type : "text" , text : "ok" } ] ) ;
13151344 } ) ;
13161345
1346+ it ( "times out an authorized_user ADC token refresh" , async ( ) => {
1347+ const tempDir = await mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-google-vertex-adc-timeout-" ) ) ;
1348+ const credentialsPath = path . join ( tempDir , "application_default_credentials.json" ) ;
1349+ await writeFile (
1350+ credentialsPath ,
1351+ JSON . stringify ( {
1352+ type : "authorized_user" ,
1353+ client_id : "client-id" ,
1354+ client_secret : "client-secret" ,
1355+ refresh_token : "timeout-refresh-token" ,
1356+ } ) ,
1357+ "utf8" ,
1358+ ) ;
1359+ vi . stubEnv ( "GOOGLE_APPLICATION_CREDENTIALS" , credentialsPath ) ;
1360+ vi . useFakeTimers ( ) ;
1361+
1362+ let observedSignal : AbortSignal | undefined ;
1363+ const tokenFetchMock = vi . fn ( ( _input : string | URL | Request , init ?: RequestInit ) => {
1364+ const signal = init ?. signal ;
1365+ if ( ! signal ) {
1366+ throw new Error ( "expected token refresh deadline signal" ) ;
1367+ }
1368+ observedSignal = signal ;
1369+ const body = new ReadableStream < Uint8Array > ( {
1370+ start ( controller ) {
1371+ signal . addEventListener ( "abort" , ( ) => controller . error ( signal . reason ) , { once : true } ) ;
1372+ } ,
1373+ } ) ;
1374+ return Promise . resolve ( new Response ( body , { status : 200 } ) ) ;
1375+ } ) ;
1376+
1377+ const pendingRefresh = resolveGoogleVertexAuthorizedUserHeaders ( tokenFetchMock ) ;
1378+ // Attach the rejection handler before advancing fake time so the expected
1379+ // timeout cannot surface as an unhandled rejection between timer ticks.
1380+ const refreshError = pendingRefresh . catch ( ( error : unknown ) => error ) ;
1381+ await vi . waitFor ( ( ) => expect ( tokenFetchMock ) . toHaveBeenCalledOnce ( ) ) ;
1382+ const signal = observedSignal ;
1383+ if ( ! signal ) {
1384+ throw new Error ( "expected token refresh deadline signal" ) ;
1385+ }
1386+ expect ( signal . aborted ) . toBe ( false ) ;
1387+ await vi . advanceTimersByTimeAsync ( 30_000 ) ;
1388+
1389+ expect ( signal . aborted ) . toBe ( true ) ;
1390+ await expect ( refreshError ) . resolves . toMatchObject ( {
1391+ name : "TimeoutError" ,
1392+ message : "request timed out" ,
1393+ } ) ;
1394+ } ) ;
1395+
13171396 it ( "refreshes authorized_user ADC from a compressed token response" , async ( ) => {
13181397 const tempDir = await mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-google-vertex-adc-gzip-" ) ) ;
13191398 const credentialsPath = path . join ( tempDir , "application_default_credentials.json" ) ;
0 commit comments