@@ -14,7 +14,7 @@ class XhrMock {
1414  public  static  captures : any [ ]  =  [ ] ; 
1515  public  static  DONE  =  4 ; 
1616
17-   private  captureArgs  = 
17+   protected  captureArgs  = 
1818    ( caller : string )  => 
1919    ( ...args : any [ ] )  =>  { 
2020      XhrMock . captures . push ( [ caller ,  ...args ] ) ; 
@@ -244,4 +244,94 @@ describe(XhrHttpHandler.name, () => {
244244      [ "getAllResponseHeaders" ] , 
245245    ] ) ; 
246246  } ) ; 
247+ 
248+   describe ( "per-request requestTimeout" ,  ( )  =>  { 
249+     it ( "should use per-request timeout over handler config timeout" ,  async  ( )  =>  { 
250+       const  handler  =  new  XhrHttpHandler ( {  requestTimeout : 5000  } ) ; 
251+ 
252+       const  requestTimeoutSpy  =  vi . spyOn ( await  import ( "./request-timeout" ) ,  "requestTimeout" ) ; 
253+ 
254+       const  mockRequest  =  new  HttpRequest ( { 
255+         method : "GET" , 
256+         hostname : "example.com" , 
257+         protocol : "https:" , 
258+         path : "/" , 
259+         headers : { } , 
260+       } ) ; 
261+ 
262+       class  TimeoutXhrMock  extends  XhrMock  { 
263+         send ( ...args : any [ ] )  { 
264+           this . captureArgs ( "send" ) ( ...args ) ; 
265+           // let it timeout 
266+         } 
267+       } 
268+ 
269+       ( global  as  any ) . XMLHttpRequest  =  TimeoutXhrMock ; 
270+ 
271+       try  { 
272+         await  handler . handle ( mockRequest ,  {  requestTimeout : 100  } ) ; 
273+       }  catch  ( error )  { 
274+         // expected to timeout 
275+       } 
276+ 
277+       // verify requestTimeout function was called with per-request timeout (100), not handler timeout (5000) 
278+       expect ( requestTimeoutSpy ) . toHaveBeenCalledWith ( 100 ) ; 
279+ 
280+       requestTimeoutSpy . mockRestore ( ) ; 
281+       ( global  as  any ) . XMLHttpRequest  =  XhrMock ;  // restore original mock 
282+     } ) ; 
283+ 
284+     it ( "should fall back to handler config timeout when per-request timeout not provided" ,  async  ( )  =>  { 
285+       const  handler  =  new  XhrHttpHandler ( {  requestTimeout : 200  } ) ; 
286+ 
287+       const  requestTimeoutSpy  =  vi . spyOn ( await  import ( "./request-timeout" ) ,  "requestTimeout" ) ; 
288+ 
289+       const  mockRequest  =  new  HttpRequest ( { 
290+         method : "GET" , 
291+         hostname : "example.com" , 
292+         protocol : "https:" , 
293+         path : "/" , 
294+         headers : { } , 
295+       } ) ; 
296+ 
297+       class  TimeoutXhrMock  extends  XhrMock  { 
298+         send ( ...args : any [ ] )  { 
299+           this . captureArgs ( "send" ) ( ...args ) ; 
300+         } 
301+       } 
302+ 
303+       ( global  as  any ) . XMLHttpRequest  =  TimeoutXhrMock ; 
304+ 
305+       try  { 
306+         await  handler . handle ( mockRequest ,  { } ) ; 
307+       }  catch  ( error )  { } 
308+ 
309+       expect ( requestTimeoutSpy ) . toHaveBeenCalledWith ( 200 ) ; 
310+ 
311+       requestTimeoutSpy . mockRestore ( ) ; 
312+       ( global  as  any ) . XMLHttpRequest  =  XhrMock ; 
313+     } ) ; 
314+ 
315+     it ( "should handle zero timeout correctly" ,  async  ( )  =>  { 
316+       const  handler  =  new  XhrHttpHandler ( {  requestTimeout : 1000  } ) ; 
317+ 
318+       const  requestTimeoutSpy  =  vi . spyOn ( await  import ( "./request-timeout" ) ,  "requestTimeout" ) ; 
319+ 
320+       const  mockRequest  =  new  HttpRequest ( { 
321+         method : "GET" , 
322+         hostname : "example.com" , 
323+         protocol : "https:" , 
324+         path : "/" , 
325+         headers : { } , 
326+       } ) ; 
327+ 
328+       try  { 
329+         await  handler . handle ( mockRequest ,  {  requestTimeout : 0  } ) ; 
330+       }  catch  ( error )  { } 
331+ 
332+       expect ( requestTimeoutSpy ) . toHaveBeenCalledWith ( 0 ) ; 
333+ 
334+       requestTimeoutSpy . mockRestore ( ) ; 
335+     } ) ; 
336+   } ) ; 
247337} ) ; 
0 commit comments