@@ -3,6 +3,7 @@ import type { stat as NodeStat } from 'node:fs/promises'
33import type { OnSuccessPayload , UploadOptions } from 'tus-js-client'
44
55import { PassThrough } from 'node:stream'
6+ import { setImmediate as delayImmediate } from 'node:timers/promises'
67
78import { beforeEach , describe , expect , it , vi } from 'vitest'
89
@@ -270,4 +271,40 @@ describe('sendTusRequest', () => {
270271
271272 expect ( uploadCtor ) . not . toHaveBeenCalled ( )
272273 } )
274+
275+ it ( 'does not leave bookkeeping promises unhandled when an awaited upload fails' , async ( ) => {
276+ statMock . mockResolvedValue ( { size : 2048 } )
277+
278+ const uploadError = new Error ( 'connect ETIMEDOUT 5.161.182.178:443' )
279+ const unhandledRejections : unknown [ ] = [ ]
280+ const onUnhandledRejection = ( reason : unknown ) : void => {
281+ unhandledRejections . push ( reason )
282+ }
283+
284+ startBehavior = ( options ) => {
285+ options . onError ?.( uploadError )
286+ }
287+
288+ process . on ( 'unhandledRejection' , onUnhandledRejection )
289+ try {
290+ await expect (
291+ sendTusRequest ( {
292+ streamsMap : {
293+ failed : { path : '/tmp/failed' , stream : new PassThrough ( ) } ,
294+ } ,
295+ assembly : baseAssembly ,
296+ requestedChunkSize : 1_048_576 ,
297+ uploadConcurrency : 1 ,
298+ onProgress : ( ) => { } ,
299+ } ) ,
300+ ) . rejects . toBe ( uploadError )
301+
302+ await delayImmediate ( )
303+ await delayImmediate ( )
304+
305+ expect ( unhandledRejections ) . toEqual ( [ ] )
306+ } finally {
307+ process . off ( 'unhandledRejection' , onUnhandledRejection )
308+ }
309+ } )
273310} )
0 commit comments