@@ -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