14
14
* See the License for the specific language governing permissions and
15
15
* limitations under the License.
16
16
*/
17
- import { assert , expect } from 'chai' ;
17
+ import { assert , expect , use } from 'chai' ;
18
18
import { FbsBlob } from '../../src/implementation/blob' ;
19
19
import { Location } from '../../src/implementation/location' ;
20
20
import { Unsubscribe } from '../../src/implementation/observer' ;
@@ -31,8 +31,13 @@ import {
31
31
} from './testshared' ;
32
32
import { injectTestConnection } from '../../src/platform/connection' ;
33
33
import { Deferred } from '@firebase/util' ;
34
- import { retryLimitExceeded } from '../../src/implementation/error' ;
34
+ import { canceled , retryLimitExceeded } from '../../src/implementation/error' ;
35
35
import { 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 ) ;
36
41
37
42
const testLocation = new Location ( 'bucket' , 'object' ) ;
38
43
const smallBlob = new FbsBlob ( new Uint8Array ( [ 97 ] ) ) ;
@@ -361,7 +366,7 @@ describe('Firebase Storage > Upload Task', () => {
361
366
function handleStateChange (
362
367
requestHandler : RequestHandler ,
363
368
blob : FbsBlob
364
- ) : Promise < TotalState > {
369
+ ) : { taskPromise : Promise < TotalState > ; task : UploadTask } {
365
370
const storageService = storageServiceWithHandler ( requestHandler ) ;
366
371
const task = new UploadTask (
367
372
new Reference ( storageService , testLocation ) ,
@@ -410,7 +415,7 @@ describe('Firebase Storage > Upload Task', () => {
410
415
}
411
416
) ;
412
417
413
- return deferred . promise ;
418
+ return { taskPromise : deferred . promise , task } ;
414
419
}
415
420
416
421
it ( 'Calls callback sequences for small uploads correctly' , ( ) => {
@@ -422,13 +427,13 @@ describe('Firebase Storage > Upload Task', () => {
422
427
it ( 'properly times out if large blobs returns a 503 when finalizing' , async ( ) => {
423
428
clock = useFakeTimers ( ) ;
424
429
// Kick off upload
425
- const promise = handleStateChange (
430
+ const { taskPromise } = handleStateChange (
426
431
fake503ForFinalizeServerHandler ( ) ,
427
432
bigBlob
428
433
) ;
429
434
// Run all timers
430
435
await clock . runAllAsync ( ) ;
431
- const { events, progress } = await promise ;
436
+ const { events, progress } = await taskPromise ;
432
437
expect ( events . length ) . to . equal ( 2 ) ;
433
438
expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
434
439
expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
@@ -460,10 +465,13 @@ describe('Firebase Storage > Upload Task', () => {
460
465
it ( 'properly times out if large blobs returns a 503 when uploading' , async ( ) => {
461
466
clock = useFakeTimers ( ) ;
462
467
// Kick off upload
463
- const promise = handleStateChange ( fake503ForUploadServerHandler ( ) , bigBlob ) ;
468
+ const { taskPromise } = handleStateChange (
469
+ fake503ForUploadServerHandler ( ) ,
470
+ bigBlob
471
+ ) ;
464
472
// Run all timers
465
473
await clock . runAllAsync ( ) ;
466
- const { events, progress } = await promise ;
474
+ const { events, progress } = await taskPromise ;
467
475
expect ( events . length ) . to . equal ( 2 ) ;
468
476
expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
469
477
expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
@@ -478,13 +486,122 @@ describe('Firebase Storage > Upload Task', () => {
478
486
} ) ;
479
487
clock . restore ( ) ;
480
488
} ) ;
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
+ } ) ;
481
595
it ( 'tests if small requests that respond with 500 retry correctly' , async ( ) => {
482
596
clock = useFakeTimers ( ) ;
483
597
// Kick off upload
484
- const promise = handleStateChange ( fakeOneShot503ServerHandler ( ) , smallBlob ) ;
598
+ const { taskPromise } = handleStateChange (
599
+ fakeOneShot503ServerHandler ( ) ,
600
+ smallBlob
601
+ ) ;
485
602
// Run all timers
486
603
await clock . runAllAsync ( ) ;
487
- const { events, progress } = await promise ;
604
+ const { events, progress } = await taskPromise ;
488
605
expect ( events . length ) . to . equal ( 2 ) ;
489
606
expect ( events [ 0 ] ) . to . deep . equal ( { type : 'resume' } ) ;
490
607
expect ( events [ 1 ] . type ) . to . deep . equal ( 'error' ) ;
0 commit comments