1414 * See the License for the specific language governing permissions and
1515 * limitations under the License.
1616 */
17- import { assert , expect } from 'chai' ;
17+ import { assert , expect , use } from 'chai' ;
1818import { FbsBlob } from '../../src/implementation/blob' ;
1919import { Location } from '../../src/implementation/location' ;
2020import { Unsubscribe } from '../../src/implementation/observer' ;
@@ -31,8 +31,13 @@ import {
3131} from './testshared' ;
3232import { injectTestConnection } from '../../src/platform/connection' ;
3333import { Deferred } from '@firebase/util' ;
34- import { retryLimitExceeded } from '../../src/implementation/error' ;
34+ import { canceled , retryLimitExceeded } from '../../src/implementation/error' ;
3535import { SinonFakeTimers , useFakeTimers } from 'sinon' ;
36+ import * as sinon from 'sinon' ;
37+ import sinonChai from 'sinon-chai' ;
38+ import { DEFAULT_MAX_UPLOAD_RETRY_TIME } from '../../src/implementation/constants' ;
39+
40+ use ( sinonChai ) ;
3641
3742const testLocation = new Location ( 'bucket' , 'object' ) ;
3843const smallBlob = new FbsBlob ( new Uint8Array ( [ 97 ] ) ) ;
@@ -361,7 +366,7 @@ describe('Firebase Storage > Upload Task', () => {
361366 function handleStateChange (
362367 requestHandler : RequestHandler ,
363368 blob : FbsBlob
364- ) : Promise < TotalState > {
369+ ) : { taskPromise : Promise < TotalState > ; task : UploadTask } {
365370 const storageService = storageServiceWithHandler ( requestHandler ) ;
366371 const task = new UploadTask (
367372 new Reference ( storageService , testLocation ) ,
@@ -410,7 +415,7 @@ describe('Firebase Storage > Upload Task', () => {
410415 }
411416 ) ;
412417
413- return deferred . promise ;
418+ return { taskPromise : deferred . promise , task } ;
414419 }
415420
416421 it ( 'Calls callback sequences for small uploads correctly' , ( ) => {
@@ -422,13 +427,13 @@ describe('Firebase Storage > Upload Task', () => {
422427 it ( 'properly times out if large blobs returns a 503 when finalizing' , async ( ) => {
423428 clock = useFakeTimers ( ) ;
424429 // Kick off upload
425- const promise = handleStateChange (
430+ const { taskPromise } = handleStateChange (
426431 fake503ForFinalizeServerHandler ( ) ,
427432 bigBlob
428433 ) ;
429434 // Run all timers
430435 await clock . runAllAsync ( ) ;
431- const { events, progress } = await promise ;
436+ const { events, progress } = await taskPromise ;
432437 expect ( events . length ) . to . equal ( 2 ) ;
433438 expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
434439 expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
@@ -460,10 +465,13 @@ describe('Firebase Storage > Upload Task', () => {
460465 it ( 'properly times out if large blobs returns a 503 when uploading' , async ( ) => {
461466 clock = useFakeTimers ( ) ;
462467 // Kick off upload
463- const promise = handleStateChange ( fake503ForUploadServerHandler ( ) , bigBlob ) ;
468+ const { taskPromise } = handleStateChange (
469+ fake503ForUploadServerHandler ( ) ,
470+ bigBlob
471+ ) ;
464472 // Run all timers
465473 await clock . runAllAsync ( ) ;
466- const { events, progress } = await promise ;
474+ const { events, progress } = await taskPromise ;
467475 expect ( events . length ) . to . equal ( 2 ) ;
468476 expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
469477 expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
@@ -478,13 +486,122 @@ describe('Firebase Storage > Upload Task', () => {
478486 } ) ;
479487 clock . restore ( ) ;
480488 } ) ;
489+
490+ /**
491+ * Starts upload, finds the first instance of an exponential backoff, and resolves `readyToCancel` when done.
492+ * @returns readyToCancel, taskPromise and task
493+ */
494+ function resumeCancelSetup ( ) : {
495+ readyToCancel : Promise < null > ;
496+ taskPromise : Promise < TotalState > ;
497+ task : UploadTask ;
498+ } {
499+ clock = useFakeTimers ( ) ;
500+ const fakeSetTimeout = clock . setTimeout ;
501+
502+ let gotFirstEvent = false ;
503+
504+ const stub = sinon . stub ( global , 'setTimeout' ) ;
505+
506+ // Function that notifies when we are in the middle of an exponential backoff
507+ const readyToCancel = new Promise < null > ( resolve => {
508+ stub . callsFake ( ( fn , timeout ) => {
509+ // @ts -ignore The types for `stub.callsFake` is incompatible with types of `clock.setTimeout`
510+ const res = fakeSetTimeout ( fn , timeout ) ;
511+ if ( timeout !== DEFAULT_MAX_UPLOAD_RETRY_TIME ) {
512+ if ( ! gotFirstEvent || timeout === 0 ) {
513+ clock . tick ( timeout as number ) ;
514+ } else {
515+ // If the timeout isn't 0 and it isn't the max upload retry time, it's most likely due to exponential backoff.
516+ resolve ( null ) ;
517+ }
518+ }
519+ return res ;
520+ } ) ;
521+ } ) ;
522+ readyToCancel . then (
523+ ( ) => stub . restore ( ) ,
524+ ( ) => stub . restore ( )
525+ ) ;
526+ return {
527+ ...handleStateChange (
528+ fake503ForUploadServerHandler ( undefined , ( ) => ( gotFirstEvent = true ) ) ,
529+ bigBlob
530+ ) ,
531+ readyToCancel
532+ } ;
533+ }
534+ it ( 'properly errors with a cancel StorageError if a pending timeout remains' , async ( ) => {
535+ // Kick off upload
536+ const { readyToCancel, taskPromise : promise , task } = resumeCancelSetup ( ) ;
537+
538+ await readyToCancel ;
539+ task . cancel ( ) ;
540+
541+ const { events, progress } = await promise ;
542+ expect ( events . length ) . to . equal ( 2 ) ;
543+ expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
544+ expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
545+ const canceledError = canceled ( ) ;
546+ expect ( events [ 1 ] . data ! . name ) . to . deep . equal ( canceledError . name ) ;
547+ expect ( events [ 1 ] . data ! . message ) . to . deep . equal ( canceledError . message ) ;
548+ const blobSize = bigBlob . size ( ) ;
549+ expect ( progress . length ) . to . equal ( 1 ) ;
550+ expect ( progress [ 0 ] ) . to . deep . equal ( {
551+ bytesTransferred : 0 ,
552+ totalBytes : blobSize
553+ } ) ;
554+ expect ( clock . countTimers ( ) ) . to . eq ( 0 ) ;
555+ clock . restore ( ) ;
556+ } ) ;
557+ it ( 'properly errors with a pause StorageError if a pending timeout remains' , async ( ) => {
558+ // Kick off upload
559+ const { readyToCancel, taskPromise : promise , task } = resumeCancelSetup ( ) ;
560+
561+ await readyToCancel ;
562+
563+ task . pause ( ) ;
564+ expect ( clock . countTimers ( ) ) . to . eq ( 0 ) ;
565+ task . resume ( ) ;
566+ await clock . runAllAsync ( ) ;
567+
568+ // Run all timers
569+ const { events, progress } = await promise ;
570+ expect ( events . length ) . to . equal ( 4 ) ;
571+ expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
572+ expect ( events [ 1 ] ) . to . deep . equal ( { type : 'pause' } ) ;
573+ expect ( events [ 2 ] ) . to . deep . equal ( { type : 'resume' } ) ;
574+ expect ( events [ 3 ] . type ) . to . deep . equal ( 'error' ) ;
575+ const retryError = retryLimitExceeded ( ) ;
576+ expect ( events [ 3 ] . data ! . name ) . to . deep . equal ( retryError . name ) ;
577+ expect ( events [ 3 ] . data ! . message ) . to . deep . equal ( retryError . message ) ;
578+ const blobSize = bigBlob . size ( ) ;
579+ expect ( progress . length ) . to . equal ( 3 ) ;
580+ expect ( progress [ 0 ] ) . to . deep . equal ( {
581+ bytesTransferred : 0 ,
582+ totalBytes : blobSize
583+ } ) ;
584+ expect ( progress [ 1 ] ) . to . deep . equal ( {
585+ bytesTransferred : 0 ,
586+ totalBytes : blobSize
587+ } ) ;
588+ expect ( progress [ 2 ] ) . to . deep . equal ( {
589+ bytesTransferred : 0 ,
590+ totalBytes : blobSize
591+ } ) ;
592+ expect ( clock . countTimers ( ) ) . to . eq ( 0 ) ;
593+ clock . restore ( ) ;
594+ } ) ;
481595 it ( 'tests if small requests that respond with 500 retry correctly' , async ( ) => {
482596 clock = useFakeTimers ( ) ;
483597 // Kick off upload
484- const promise = handleStateChange ( fakeOneShot503ServerHandler ( ) , smallBlob ) ;
598+ const { taskPromise } = handleStateChange (
599+ fakeOneShot503ServerHandler ( ) ,
600+ smallBlob
601+ ) ;
485602 // Run all timers
486603 await clock . runAllAsync ( ) ;
487- const { events, progress } = await promise ;
604+ const { events, progress } = await taskPromise ;
488605 expect ( events . length ) . to . equal ( 2 ) ;
489606 expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
490607 expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
0 commit comments