@@ -49,6 +49,7 @@ import {
4949} from '../../src/nodejs-common/util.js' ;
5050import { DEFAULT_PROJECT_ID_TOKEN } from '../../src/nodejs-common/service.js' ;
5151import duplexify from 'duplexify' ;
52+ import { EventEmitter , Writable } from 'stream' ;
5253
5354nock . disableNetConnect ( ) ;
5455
@@ -1189,6 +1190,100 @@ describe('common/util', () => {
11891190 } ) ;
11901191 } ) ;
11911192
1193+ describe ( 'TLS handshake errors' , ( ) => {
1194+ const error = new Error ( '🤮' ) ;
1195+
1196+ beforeEach ( ( ) => {
1197+ authClient . authorizeRequest = async ( ) => {
1198+ throw error ;
1199+ } ;
1200+ } ) ;
1201+
1202+ it ( 'should transform raw ECONNRESET into TLS ApiError' , done => {
1203+ const networkError = new Error ( 'ECONNRESET' ) ;
1204+ sandbox . stub ( fakeGoogleAuth , 'GoogleAuth' ) . returns ( authClient ) ;
1205+
1206+ createMakeRequestStub ( sandbox , networkError , util , authClient ) ;
1207+ const makeAuthenticatedRequest = util . makeAuthenticatedRequestFactory (
1208+ { }
1209+ ) ;
1210+
1211+ makeAuthenticatedRequest ( { } as DecorateRequestOptions , err => {
1212+ assert . ok ( err ) ;
1213+ assert . strictEqual ( ( err as ApiError ) . code , 408 ) ;
1214+ assert . strictEqual (
1215+ ( err as ApiError ) . message ,
1216+ 'TLS handshake timeout. This may be due to CPU starvation.'
1217+ ) ;
1218+ done ( ) ;
1219+ } ) ;
1220+ } ) ;
1221+
1222+ it ( 'should transform raw "TLS handshake" into TLS ApiError' , done => {
1223+ const networkError = new Error (
1224+ 'Request failed due to TLS handshake timeout.'
1225+ ) ;
1226+ sandbox . stub ( fakeGoogleAuth , 'GoogleAuth' ) . returns ( authClient ) ;
1227+ createMakeRequestStub ( sandbox , networkError , util , authClient ) ;
1228+
1229+ const makeAuthenticatedRequest = util . makeAuthenticatedRequestFactory (
1230+ { }
1231+ ) ;
1232+
1233+ makeAuthenticatedRequest ( { } as DecorateRequestOptions , err => {
1234+ assert . ok ( err ) ;
1235+ assert . strictEqual ( ( err as ApiError ) . code , 408 ) ;
1236+ assert . strictEqual (
1237+ ( err as ApiError ) . message ,
1238+ 'TLS handshake timeout. This may be due to CPU starvation.'
1239+ ) ;
1240+ done ( ) ;
1241+ } ) ;
1242+ } ) ;
1243+
1244+ it ( 'should transform raw generic "timed out" into TLS ApiError' , done => {
1245+ const networkError = new Error ( 'The request timed out.' ) ;
1246+ sandbox . stub ( fakeGoogleAuth , 'GoogleAuth' ) . returns ( authClient ) ;
1247+ createMakeRequestStub ( sandbox , networkError , util , authClient ) ;
1248+
1249+ const makeAuthenticatedRequest = util . makeAuthenticatedRequestFactory (
1250+ { }
1251+ ) ;
1252+
1253+ makeAuthenticatedRequest ( { } as DecorateRequestOptions , err => {
1254+ assert . ok ( err ) ;
1255+ assert . strictEqual ( ( err as ApiError ) . code , 408 ) ;
1256+ assert . strictEqual (
1257+ ( err as ApiError ) . message ,
1258+ 'TLS handshake timeout. This may be due to CPU starvation.'
1259+ ) ;
1260+ done ( ) ;
1261+ } ) ;
1262+ } ) ;
1263+
1264+ it ( 'should transform raw ETIMEDOUT into TLS ApiError' , done => {
1265+ const networkError = new Error (
1266+ 'Request failed with error: ETIMEDOUT'
1267+ ) ;
1268+ sandbox . stub ( fakeGoogleAuth , 'GoogleAuth' ) . returns ( authClient ) ;
1269+ createMakeRequestStub ( sandbox , networkError , util , authClient ) ;
1270+
1271+ const makeAuthenticatedRequest = util . makeAuthenticatedRequestFactory (
1272+ { }
1273+ ) ;
1274+
1275+ makeAuthenticatedRequest ( { } as DecorateRequestOptions , err => {
1276+ assert . ok ( err ) ;
1277+ assert . strictEqual ( ( err as ApiError ) . code , 408 ) ;
1278+ assert . strictEqual (
1279+ ( err as ApiError ) . message ,
1280+ 'TLS handshake timeout. This may be due to CPU starvation.'
1281+ ) ;
1282+ done ( ) ;
1283+ } ) ;
1284+ } ) ;
1285+ } ) ;
1286+
11921287 describe ( 'authentication success' , ( ) => {
11931288 const reqOpts = fakeReqOpts ;
11941289 beforeEach ( ( ) => {
@@ -1891,3 +1986,52 @@ describe('common/util', () => {
18911986 } ) ;
18921987 } ) ;
18931988} ) ;
1989+ function createMakeRequestStub (
1990+ sandbox : sinon . SinonSandbox ,
1991+ networkError : Error ,
1992+ util : Util & { [ index : string ] : Function } ,
1993+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1994+ authClient : any
1995+ ) {
1996+ const authorizedReqOpts = { uri : 'test-uri' } as DecorateRequestOptions ;
1997+ sandbox . stub ( authClient , 'authorizeRequest' ) . resolves ( authorizedReqOpts ) ;
1998+ sandbox . stub ( authClient , 'getProjectId' ) . resolves ( 'test-project-id' ) ;
1999+
2000+ sandbox
2001+ . stub ( util , 'makeRequest' )
2002+ . callsFake ( ( _authenticatedReqOpts , cfg , callback ) => {
2003+ const mockRequestStream = new EventEmitter ( ) as unknown as Writable & {
2004+ abort : ( ) => void ;
2005+ } ;
2006+ mockRequestStream . abort = ( ) => { } ;
2007+
2008+ if ( ! cfg . stream ) {
2009+ const retryCallback = (
2010+ err : Error | null ,
2011+ response : { } ,
2012+ body : unknown
2013+ ) => {
2014+ if (
2015+ err &&
2016+ ( err . message ?. includes ( 'TLS handshake' ) ||
2017+ err . message ?. includes ( 'timed out' ) ||
2018+ err . message ?. includes ( 'ETIMEDOUT' ) ||
2019+ err . message ?. includes ( 'ECONNRESET' ) )
2020+ ) {
2021+ const tlsTimeoutError = new ApiError ( {
2022+ code : 408 ,
2023+ message :
2024+ 'TLS handshake timeout. This may be due to CPU starvation.' ,
2025+ response : response as r . Response ,
2026+ } ) ;
2027+ err = tlsTimeoutError ;
2028+ }
2029+ util . handleResp ( err , response as r . Response , body , callback ! ) ;
2030+ } ;
2031+
2032+ retryCallback ( networkError , { } as r . Response , null ) ;
2033+ }
2034+
2035+ return mockRequestStream ;
2036+ } ) ;
2037+ }
0 commit comments