@@ -18,6 +18,7 @@ import HttpPollingDatafileManager from '../src/httpPollingDatafileManager'
1818import { Headers , AbortableRequest , Response } from '../src/http'
1919import { DatafileManagerConfig } from '../src/datafileManager' ;
2020import { advanceTimersByTime , getTimerCount } from './testUtils'
21+ import PersistentKeyValueCache from '../src/persistentKeyValueCache'
2122
2223jest . mock ( '../src/backoffController' , ( ) => {
2324 return jest . fn ( ) . mockImplementation ( ( ) => {
@@ -39,6 +40,8 @@ class TestDatafileManager extends HttpPollingDatafileManager {
3940
4041 responsePromises : Promise < Response > [ ] = [ ]
4142
43+ simulateResponseDelay : boolean = false
44+
4245 makeGetRequest ( url : string , headers : Headers ) : AbortableRequest {
4346 const nextResponse : Error | Response | undefined = this . queuedResponses . pop ( )
4447 let responsePromise : Promise < Response >
@@ -47,7 +50,12 @@ class TestDatafileManager extends HttpPollingDatafileManager {
4750 } else if ( nextResponse instanceof Error ) {
4851 responsePromise = Promise . reject ( nextResponse )
4952 } else {
50- responsePromise = Promise . resolve ( nextResponse )
53+ if ( this . simulateResponseDelay ) {
54+ // Actual response will have some delay. This is required to get expected behavior for caching.
55+ responsePromise = new Promise ( ( resolve ) => setTimeout ( ( ) => resolve ( nextResponse ) , 50 ) )
56+ } else {
57+ responsePromise = Promise . resolve ( nextResponse )
58+ }
5159 }
5260 this . responsePromises . push ( responsePromise )
5361 return { responsePromise, abort : jest . fn ( ) }
@@ -58,6 +66,30 @@ class TestDatafileManager extends HttpPollingDatafileManager {
5866 }
5967}
6068
69+ const testCache : PersistentKeyValueCache = {
70+ get ( key : string ) : Promise < any | null > {
71+ let val = null
72+ switch ( key ) {
73+ case 'opt-datafile-keyThatExists' :
74+ val = { name : 'keyThatExists' }
75+ break
76+ }
77+ return Promise . resolve ( val )
78+ } ,
79+
80+ set ( key : string , val : any ) : Promise < void > {
81+ return Promise . resolve ( )
82+ } ,
83+
84+ contains ( key : string ) : Promise < Boolean > {
85+ return Promise . resolve ( false )
86+ } ,
87+
88+ remove ( key : string ) : Promise < void > {
89+ return Promise . resolve ( )
90+ }
91+ }
92+
6193describe ( 'httpPollingDatafileManager' , ( ) => {
6294 beforeEach ( ( ) => {
6395 jest . useFakeTimers ( )
@@ -82,13 +114,7 @@ describe('httpPollingDatafileManager', () => {
82114 expect ( manager . get ( ) ) . toEqual ( { foo : 'abcd' } )
83115 } )
84116
85- it ( 'resolves onReady immediately' , async ( ) => {
86- manager . start ( )
87- await manager . onReady ( )
88- expect ( manager . get ( ) ) . toEqual ( { foo : 'abcd' } )
89- } )
90-
91- it ( 'after being started, fetches the datafile, updates itself, emits an update event, and updates itself again after a timeout' , async ( ) => {
117+ it ( 'after being started, fetches the datafile, updates itself, and updates itself again after a timeout' , async ( ) => {
92118 manager . queuedResponses . push (
93119 {
94120 statusCode : 200 ,
@@ -106,10 +132,6 @@ describe('httpPollingDatafileManager', () => {
106132 manager . start ( )
107133 expect ( manager . responsePromises . length ) . toBe ( 1 )
108134 await manager . responsePromises [ 0 ]
109- expect ( updateFn ) . toBeCalledTimes ( 1 )
110- expect ( updateFn ) . toBeCalledWith ( {
111- datafile : { foo : 'bar' }
112- } )
113135 expect ( manager . get ( ) ) . toEqual ( { foo : 'bar' } )
114136 updateFn . mockReset ( )
115137
@@ -134,27 +156,15 @@ describe('httpPollingDatafileManager', () => {
134156 expect ( manager . get ( ) ) . toEqual ( { foo : 'abcd' } )
135157 } )
136158
137- it ( 'after being started, resolves onReady immediately' , async ( ) => {
138- manager . start ( )
139- await manager . onReady ( )
140- expect ( manager . get ( ) ) . toEqual ( { foo : 'abcd' } )
141- } )
142-
143- it ( 'after being started, fetches the datafile, updates itself once, and emits an update event, but does not schedule a future update' , async ( ) => {
159+ it ( 'after being started, fetches the datafile, updates itself once, but does not schedule a future update' , async ( ) => {
144160 manager . queuedResponses . push ( {
145161 statusCode : 200 ,
146162 body : '{"foo": "bar"}' ,
147163 headers : { }
148164 } )
149- const updateFn = jest . fn ( )
150- manager . on ( 'update' , updateFn )
151165 manager . start ( )
152166 expect ( manager . responsePromises . length ) . toBe ( 1 )
153167 await manager . responsePromises [ 0 ]
154- expect ( updateFn ) . toBeCalledTimes ( 1 )
155- expect ( updateFn ) . toBeCalledWith ( {
156- datafile : { foo : 'bar' }
157- } )
158168 expect ( manager . get ( ) ) . toEqual ( { foo : 'bar' } )
159169 expect ( getTimerCount ( ) ) . toBe ( 0 )
160170 } )
@@ -634,4 +644,88 @@ describe('httpPollingDatafileManager', () => {
634644 expect ( makeGetRequestSpy ) . toBeCalledTimes ( 2 )
635645 } )
636646 } )
647+
648+ describe ( 'when constructed with a cache implementation having an already cached datafile' , ( ) => {
649+ beforeEach ( ( ) => {
650+ manager = new TestDatafileManager ( {
651+ sdkKey : 'keyThatExists' ,
652+ updateInterval : 500 ,
653+ autoUpdate : true ,
654+ cache : testCache ,
655+ } )
656+ manager . simulateResponseDelay = true
657+ } )
658+
659+ it ( 'uses cached version of datafile first and resolves the promise while network throws error and no update event is triggered' , async ( ) => {
660+ manager . queuedResponses . push ( new Error ( 'Connection Error' ) )
661+ const updateFn = jest . fn ( )
662+ manager . on ( 'update' , updateFn )
663+ manager . start ( )
664+ await manager . onReady ( )
665+ expect ( manager . get ( ) ) . toEqual ( { name : 'keyThatExists' } )
666+ await advanceTimersByTime ( 50 )
667+ expect ( manager . get ( ) ) . toEqual ( { name : 'keyThatExists' } )
668+ expect ( updateFn ) . toBeCalledTimes ( 0 )
669+ } )
670+
671+ it ( 'uses cached datafile, resolves ready promise, fetches new datafile from network and triggers update event' , async ( ) => {
672+ manager . queuedResponses . push ( {
673+ statusCode : 200 ,
674+ body : '{"foo": "bar"}' ,
675+ headers : { }
676+ } )
677+
678+ const updateFn = jest . fn ( )
679+ manager . on ( 'update' , updateFn )
680+ manager . start ( )
681+ await manager . onReady ( )
682+ expect ( manager . get ( ) ) . toEqual ( { name : 'keyThatExists' } )
683+ expect ( updateFn ) . toBeCalledTimes ( 0 )
684+ await advanceTimersByTime ( 50 )
685+ expect ( manager . get ( ) ) . toEqual ( { foo : 'bar' } )
686+ expect ( updateFn ) . toBeCalledTimes ( 1 )
687+ } )
688+
689+ it ( 'sets newly recieved datafile in to cache' , async ( ) => {
690+ const cacheSetSpy = jest . spyOn ( testCache , 'set' )
691+ manager . queuedResponses . push ( {
692+ statusCode : 200 ,
693+ body : '{"foo": "bar"}' ,
694+ headers : { }
695+ } )
696+ manager . start ( )
697+ await manager . onReady ( )
698+ await advanceTimersByTime ( 50 )
699+ expect ( manager . get ( ) ) . toEqual ( { foo : 'bar' } )
700+ expect ( cacheSetSpy ) . toBeCalledWith ( 'opt-datafile-keyThatExists' , { "foo" : "bar" } )
701+ } )
702+ } )
703+
704+ describe ( 'when constructed with a cache implementation without an already cached datafile' , ( ) => {
705+ beforeEach ( ( ) => {
706+ manager = new TestDatafileManager ( {
707+ sdkKey : 'keyThatDoesExists' ,
708+ updateInterval : 500 ,
709+ autoUpdate : true ,
710+ cache : testCache ,
711+ } )
712+ manager . simulateResponseDelay = true
713+ } )
714+
715+ it ( 'does not find cached datafile, fetches new datafile from network, resolves promise and does not trigger update event' , async ( ) => {
716+ manager . queuedResponses . push ( {
717+ statusCode : 200 ,
718+ body : '{"foo": "bar"}' ,
719+ headers : { }
720+ } )
721+
722+ const updateFn = jest . fn ( )
723+ manager . on ( 'update' , updateFn )
724+ manager . start ( )
725+ await advanceTimersByTime ( 50 )
726+ await manager . onReady ( )
727+ expect ( manager . get ( ) ) . toEqual ( { foo : 'bar' } )
728+ expect ( updateFn ) . toBeCalledTimes ( 0 )
729+ } )
730+ } )
637731} )
0 commit comments