@@ -341,7 +341,142 @@ describe('Firebase Storage > Upload Task', () => {
341341 } ) ;
342342
343343 task . pause ( ) ;
344+ return promise ;
345+ }
346+
347+ // This is to test to make sure that when you pause in the middle of a request that you do not get an error
348+ async function runProgressPauseTest ( blob : FbsBlob ) : Promise < void > {
349+ let callbackCount = 0 ;
350+ // Usually the first request is to create the resumable upload and the second is to upload.
351+ // Upload requests are not retryable, and this callback is to make sure we pause before the response comes back.
352+ function shouldRespondCallback ( ) : boolean {
353+ if ( callbackCount ++ === 1 ) {
354+ task . pause ( ) ;
355+ return false ;
356+ }
357+ return true ;
358+ }
359+ const storageService = storageServiceWithHandler (
360+ fakeServerHandler ( ) ,
361+ shouldRespondCallback
362+ ) ;
363+ const task = new UploadTask (
364+ new Reference ( storageService , testLocation ) ,
365+ blob
366+ ) ;
367+
368+ // eslint-disable-next-line @typescript-eslint/ban-types
369+ let resolve : Function , reject : Function ;
370+ const promise = new Promise < void > ( ( innerResolve , innerReject ) => {
371+ resolve = innerResolve ;
372+ reject = innerReject ;
373+ } ) ;
374+
375+ // Assert functions throw Errors, which means if we're not in the right stack
376+ // the error might not get reported properly. This function wraps existing
377+ // assert functions, returning a new function that calls reject with any
378+ // caught errors. This should make sure errors are reported properly.
379+ function promiseAssertWrapper < T > ( func : T ) : T {
380+ function wrapped ( ..._args : any [ ] ) : void {
381+ try {
382+ ( func as any as ( ...args : any [ ] ) => void ) . apply ( null , _args ) ;
383+ } catch ( e ) {
384+ reject ( e ) ;
385+ pausedStateCompleted . reject ( e ) ;
386+ // also throw to further unwind the stack
387+ throw e ;
388+ }
389+ }
390+ return wrapped as any as T ;
391+ }
392+
393+ const fixedAssertEquals = promiseAssertWrapper ( assert . equal ) ;
394+ const fixedAssertFalse = promiseAssertWrapper ( assert . isFalse ) ;
395+ const fixedAssertTrue = promiseAssertWrapper ( assert . isTrue ) ;
396+ const fixedAssertFail = promiseAssertWrapper ( assert . fail ) ;
397+
398+ const events : string [ ] = [ ] ;
399+ const progress : number [ ] [ ] = [ ] ;
400+ // Promise for when we are finally in the pause state
401+ const pausedStateCompleted = new Deferred ( ) ;
402+ let complete = 0 ;
403+ // Adds a callback for when the state has changed. The callback resolves the pausedStateCompleted promise
404+ // to let our test know when to resume.
405+ function addCallbacks ( task : UploadTask ) : void {
406+ let lastState : string ;
407+ task . on (
408+ TaskEvent . STATE_CHANGED ,
409+ snapshot => {
410+ fixedAssertEquals ( complete , 0 ) ;
411+
412+ const state = snapshot . state ;
413+ if ( lastState !== TaskState . RUNNING && state === TaskState . RUNNING ) {
414+ events . push ( 'resume' ) ;
415+ } else if (
416+ lastState !== TaskState . PAUSED &&
417+ state === TaskState . PAUSED
418+ ) {
419+ pausedStateCompleted . resolve ( ) ;
420+ events . push ( 'pause' ) ;
421+ }
422+
423+ const p = [ snapshot . bytesTransferred , snapshot . totalBytes ] ;
424+
425+ progress . push ( p ) ;
426+
427+ lastState = state ;
428+ } ,
429+ ( ) => {
430+ fixedAssertFail ( 'Failed to upload' ) ;
431+ } ,
432+ ( ) => {
433+ events . push ( 'complete' ) ;
434+ complete ++ ;
435+ }
436+ ) ;
437+ }
438+
439+ addCallbacks ( task ) ;
344440
441+ let completeTriggered = false ;
442+
443+ // We should clean this up and just add all callbacks in one function call.
444+ // Keeps track of all events that were logged before and asserts on them.
445+ task . on ( TaskEvent . STATE_CHANGED , undefined , undefined , ( ) => {
446+ fixedAssertFalse ( completeTriggered ) ;
447+ completeTriggered = true ;
448+
449+ fixedAssertEquals ( events . length , 4 ) ;
450+ fixedAssertEquals ( events [ 0 ] , 'resume' ) ;
451+ fixedAssertEquals ( events [ 1 ] , 'pause' ) ;
452+ fixedAssertEquals ( events [ 2 ] , 'resume' ) ;
453+ fixedAssertEquals ( events [ 3 ] , 'complete' ) ;
454+
455+ fixedAssertEquals ( complete , 1 ) ;
456+
457+ let increasing = true ;
458+ let allTotalsTheSame = true ;
459+ for ( let i = 0 ; i < progress . length - 1 ; i ++ ) {
460+ increasing = increasing && progress [ i ] [ 0 ] <= progress [ i + 1 ] [ 0 ] ;
461+ allTotalsTheSame =
462+ allTotalsTheSame && progress [ i ] [ 1 ] === progress [ i + 1 ] [ 1 ] ;
463+ }
464+
465+ let lastIsAll = false ;
466+ if ( progress . length > 0 ) {
467+ const last = progress [ progress . length - 1 ] ;
468+ lastIsAll = last [ 0 ] === last [ 1 ] ;
469+ } else {
470+ lastIsAll = true ;
471+ }
472+
473+ fixedAssertTrue ( increasing ) ;
474+ fixedAssertTrue ( allTotalsTheSame ) ;
475+ fixedAssertTrue ( lastIsAll ) ;
476+ resolve ( null ) ;
477+ } ) ;
478+ await pausedStateCompleted . promise ;
479+ task . resume ( ) ;
345480 return promise ;
346481 }
347482 enum StateType {
@@ -593,6 +728,10 @@ describe('Firebase Storage > Upload Task', () => {
593728 expect ( clock . countTimers ( ) ) . to . eq ( 0 ) ;
594729 clock . restore ( ) ;
595730 } ) ;
731+ it ( 'does not error when pausing inflight request' , async ( ) => {
732+ // Kick off upload
733+ await runProgressPauseTest ( bigBlob ) ;
734+ } ) ;
596735 it ( 'tests if small requests that respond with 500 retry correctly' , async ( ) => {
597736 clock = useFakeTimers ( ) ;
598737 // Kick off upload
0 commit comments