@@ -306,19 +306,24 @@ describe("fetchWithTimeout", () => {
306306 } ) ;
307307
308308 test ( "throws FETCH_TIMEOUT on abort" , async ( ) => {
309- // Simulate a request that gets aborted due to timeout
309+ // Simulate a slow request that will be aborted by our timeout
310+ // The mock listens to the signal and rejects when aborted
310311 mockFetch . mockImplementationOnce (
311- ( ) =>
312+ ( url , options ) =>
312313 new Promise ( ( _ , reject ) => {
313- // Immediately simulate abort behavior
314- const error = new Error ( "Aborted" ) ;
315- error . name = "AbortError" ;
316- reject ( error ) ;
314+ // When the signal aborts (from our timeout), reject with AbortError
315+ options . signal . addEventListener ( "abort" , ( ) => {
316+ const error = new Error ( "Aborted" ) ;
317+ error . name = "AbortError" ;
318+ reject ( error ) ;
319+ } ) ;
320+ // This promise would never resolve on its own - simulates slow server
317321 } ) ,
318322 ) ;
319323
320324 let error ;
321325 try {
326+ // Short timeout so test runs fast
322327 await fetchWithTimeout ( "https://api.test.com/slow" , { } , 50 ) ;
323328 } catch ( e ) {
324329 error = e ;
@@ -340,6 +345,91 @@ describe("fetchWithTimeout", () => {
340345 } ) ;
341346 } ) ;
342347
348+ test ( "respects external AbortSignal for cancellation" , async ( ) => {
349+ // Create an external controller that will abort
350+ const externalController = new AbortController ( ) ;
351+
352+ // Mock fetch to simulate a pending request that gets aborted externally
353+ mockFetch . mockImplementationOnce (
354+ ( url , options ) =>
355+ new Promise ( ( _ , reject ) => {
356+ // Listen for abort on the combined signal
357+ options . signal . addEventListener ( "abort" , ( ) => {
358+ const error = new Error ( "Aborted" ) ;
359+ error . name = "AbortError" ;
360+ reject ( error ) ;
361+ } ) ;
362+ } ) ,
363+ ) ;
364+
365+ // Start the fetch, then abort via external signal
366+ const fetchPromise = fetchWithTimeout (
367+ "https://api.test.com/slow" ,
368+ { signal : externalController . signal } ,
369+ 30000 , // Long timeout so it won't timeout first
370+ ) ;
371+
372+ // Abort via external signal
373+ externalController . abort ( ) ;
374+
375+ let error ;
376+ try {
377+ await fetchPromise ;
378+ } catch ( e ) {
379+ error = e ;
380+ }
381+
382+ // External abort should NOT be wrapped as FETCH_TIMEOUT
383+ // It should be rethrown as-is (AbortError)
384+ assert ( {
385+ given : "external signal aborts request" ,
386+ should : "throw AbortError (not FETCH_TIMEOUT)" ,
387+ actual : error ?. name ,
388+ expected : "AbortError" ,
389+ } ) ;
390+
391+ // Should NOT have FETCH_TIMEOUT code
392+ assert ( {
393+ given : "external signal aborts request" ,
394+ should : "not be classified as timeout" ,
395+ actual : error ?. cause ?. code !== "FETCH_TIMEOUT" ,
396+ expected : true ,
397+ } ) ;
398+ } ) ;
399+
400+ test ( "rejects immediately if external signal already aborted" , async ( ) => {
401+ // Create already-aborted signal
402+ const abortedController = new AbortController ( ) ;
403+ abortedController . abort ( ) ;
404+
405+ let error ;
406+ try {
407+ await fetchWithTimeout (
408+ "https://api.test.com/data" ,
409+ { signal : abortedController . signal } ,
410+ 5000 ,
411+ ) ;
412+ } catch ( e ) {
413+ error = e ;
414+ }
415+
416+ // Should throw immediately without calling fetch
417+ assert ( {
418+ given : "already-aborted signal" ,
419+ should : "throw AbortError immediately" ,
420+ actual : error ?. name ,
421+ expected : "AbortError" ,
422+ } ) ;
423+
424+ // Fetch should not have been called
425+ assert ( {
426+ given : "already-aborted signal" ,
427+ should : "not call fetch" ,
428+ actual : mockFetch . mock . calls . length ,
429+ expected : 0 ,
430+ } ) ;
431+ } ) ;
432+
343433 test ( "passes through non-timeout errors with wrapping" , async ( ) => {
344434 const networkError = new Error ( "Network failure" ) ;
345435 networkError . code = "ECONNREFUSED" ;
@@ -879,6 +969,75 @@ describe("isPathSafe", () => {
879969 expected : true ,
880970 } ) ;
881971 } ) ;
972+
973+ test ( "rejects UNC paths" , ( ) => {
974+ const result = isPathSafe ( "\\\\server\\share\\file.txt" ) ;
975+
976+ assert ( {
977+ given : "UNC path (Windows network path)" ,
978+ should : "return safe false" ,
979+ actual : result . safe ,
980+ expected : false ,
981+ } ) ;
982+
983+ assert ( {
984+ given : "UNC path rejection" ,
985+ should : "give absolute path reason" ,
986+ actual : result . reason ,
987+ expected : "Absolute paths are not allowed" ,
988+ } ) ;
989+ } ) ;
990+
991+ test ( "rejects Windows root-relative paths (single backslash)" , ( ) => {
992+ // \Windows\System32 is root-relative on Windows (resolves to current drive root)
993+ // This is an absolute path that should be rejected
994+ const result = isPathSafe ( "\\Windows\\System32" ) ;
995+
996+ assert ( {
997+ given : "Windows root-relative path (single backslash)" ,
998+ should : "return safe false" ,
999+ actual : result . safe ,
1000+ expected : false ,
1001+ } ) ;
1002+
1003+ assert ( {
1004+ given : "root-relative path rejection" ,
1005+ should : "give absolute path reason" ,
1006+ actual : result . reason ,
1007+ expected : "Absolute paths are not allowed" ,
1008+ } ) ;
1009+ } ) ;
1010+
1011+ test ( "rejects paths starting with single backslash" , ( ) => {
1012+ // Various single-backslash absolute path patterns
1013+ const testCases = [
1014+ "\\temp\\file.txt" , // \temp\file.txt
1015+ "\\Users\\data.json" , // \Users\data.json
1016+ "\\" , // Just root
1017+ ] ;
1018+
1019+ for ( const testPath of testCases ) {
1020+ const result = isPathSafe ( testPath ) ;
1021+ assert ( {
1022+ given : `path starting with backslash: ${ testPath } ` ,
1023+ should : "return safe false" ,
1024+ actual : result . safe ,
1025+ expected : false ,
1026+ } ) ;
1027+ }
1028+ } ) ;
1029+
1030+ test ( "rejects UNC paths with extended prefix" , ( ) => {
1031+ // \\?\C:\ is Windows extended-length path prefix
1032+ const result = isPathSafe ( "\\\\?\\C:\\Windows\\System32" ) ;
1033+
1034+ assert ( {
1035+ given : "UNC extended-length path" ,
1036+ should : "return safe false" ,
1037+ actual : result . safe ,
1038+ expected : false ,
1039+ } ) ;
1040+ } ) ;
8821041} ) ;
8831042
8841043// =============================================================================
0 commit comments