@@ -328,5 +328,85 @@ describe('tus-stall-detection', () => {
328328 // Stall detection should not have triggered during the onBeforeRequest delay
329329 expect ( options . onError . calls . count ( ) ) . toBe ( 0 )
330330 } )
331+
332+ it ( 'should detect stalls when progress events stop mid-upload' , async ( ) => {
333+ const testStack = new TestHttpStack ( )
334+ const file = getBlob ( 'hello world' . repeat ( 100 ) ) // Larger file for multiple progress events
335+
336+ let progressCallCount = 0
337+ let progressHandler = null
338+
339+ // Override createRequest to capture and control progress events
340+ const originalCreateRequest = testStack . createRequest . bind ( testStack )
341+ testStack . createRequest = function ( method , url ) {
342+ const req = originalCreateRequest ( method , url )
343+
344+ if ( method === 'PATCH' ) {
345+ const originalSetProgressHandler = req . setProgressHandler . bind ( req )
346+ req . setProgressHandler = function ( handler ) {
347+ progressHandler = handler
348+ originalSetProgressHandler ( handler )
349+ }
350+
351+ // Override send to simulate progress events that stop
352+ const originalSend = req . send . bind ( req )
353+ req . send = async function ( body ) {
354+ const result = originalSend ( body )
355+
356+ // Simulate some progress events then stop
357+ if ( progressHandler && body ) {
358+ const totalSize = await getBodySize ( body )
359+ // Send progress events for first 30% of upload
360+ for ( let i = 0 ; i <= 3 ; i ++ ) {
361+ progressCallCount ++
362+ progressHandler ( Math . floor ( totalSize * 0.1 * i ) )
363+ await new Promise ( resolve => setTimeout ( resolve , 50 ) )
364+ }
365+ // Then stop sending progress events to simulate a stall
366+ }
367+
368+ return result
369+ }
370+ }
371+
372+ return req
373+ }
374+
375+ const options = {
376+ httpStack : testStack ,
377+ endpoint : 'https://tus.io/uploads' ,
378+ stallDetection : {
379+ enabled : true ,
380+ checkInterval : 100 ,
381+ stallTimeout : 200 ,
382+ } ,
383+ retryDelays : null , // No retries to get immediate error
384+ onError : waitableFunction ( 'onError' ) ,
385+ onProgress : waitableFunction ( 'onProgress' ) ,
386+ }
387+
388+ const upload = new Upload ( file , options )
389+ upload . start ( )
390+
391+ // Handle the POST request
392+ const req = await testStack . nextRequest ( )
393+ expect ( req . method ) . toBe ( 'POST' )
394+ req . respondWith ( {
395+ status : 201 ,
396+ responseHeaders : {
397+ Location : '/uploads/12345' ,
398+ } ,
399+ } )
400+
401+ // The PATCH request will start sending progress events then stall
402+
403+ // Wait for stall detection to trigger
404+ const error = await options . onError . toBeCalled ( )
405+ expect ( error . message ) . toContain ( 'Upload stalled' )
406+
407+ // Verify that we received some progress events before the stall
408+ expect ( progressCallCount ) . toBeGreaterThan ( 0 )
409+ expect ( options . onProgress . calls . count ( ) ) . toBeGreaterThan ( 0 )
410+ } )
331411 } )
332412} )
0 commit comments