@@ -3,7 +3,7 @@ import { assert } from "chai";
3
3
import { EqualMatchingInjectorConfig , It , Mock , RejectedPromiseFactory , ResolvedPromiseFactory , Times } from "moq.ts" ;
4
4
import { MimicsRejectedAsyncPresetFactory , MimicsResolvedAsyncPresetFactory , Presets , ReturnsAsyncPresetFactory , RootMockProvider , ThrowsAsyncPresetFactory } from "moq.ts/internal" ;
5
5
/* eslint-enable import/no-duplicates */
6
- import { createAutoPollOptions , createKernel , createLazyLoadOptions , createManualPollOptions , FakeCache , FakeExternalCache , FakeLogger } from "./helpers/fakes" ;
6
+ import { createAutoPollOptions , createKernel , createLazyLoadOptions , createManualPollOptions , FakeCache , FakeExternalAsyncCache , FakeExternalCache , FakeLogger } from "./helpers/fakes" ;
7
7
import { ClientCacheState } from "#lib" ;
8
8
import { AutoPollConfigService , POLL_EXPIRATION_TOLERANCE_MS } from "#lib/AutoPollConfigService" ;
9
9
import { ExternalConfigCache , IConfigCache , InMemoryConfigCache } from "#lib/ConfigCatCache" ;
@@ -12,7 +12,7 @@ import { LoggerWrapper } from "#lib/ConfigCatLogger";
12
12
import { FetchResult , IConfigFetcher , IFetchResponse } from "#lib/ConfigFetcher" ;
13
13
import { LazyLoadConfigService } from "#lib/LazyLoadConfigService" ;
14
14
import { ManualPollConfigService } from "#lib/ManualPollConfigService" ;
15
- import { Config , ProjectConfig } from "#lib/ProjectConfig" ;
15
+ import { Config , IConfig , ProjectConfig } from "#lib/ProjectConfig" ;
16
16
import { AbortToken , delay } from "#lib/Utils" ;
17
17
18
18
describe ( "ConfigServiceBaseTests" , ( ) => {
@@ -369,6 +369,93 @@ describe("ConfigServiceBaseTests", () => {
369
369
} ) ;
370
370
}
371
371
372
+ for ( const useSyncCache of [ false , true ] ) {
373
+ it ( `AutoPollConfigService - Should refresh local cache in offline mode and report configChanged when new config is synced from external cache - useSyncCache: ${ useSyncCache } ` , async ( ) => {
374
+
375
+ // Arrange
376
+
377
+ const pollIntervalSeconds = 1 ;
378
+
379
+ const logger = new LoggerWrapper ( new FakeLogger ( ) ) ;
380
+ const fakeExternalCache = useSyncCache ? new FakeExternalCache ( ) : new FakeExternalAsyncCache ( 50 ) ;
381
+ const cache = new ExternalConfigCache ( fakeExternalCache , logger ) ;
382
+
383
+ const clientReadyEvents : ClientCacheState [ ] = [ ] ;
384
+ const configChangedEvents : IConfig [ ] = [ ] ;
385
+
386
+ const options = createAutoPollOptions (
387
+ "APIKEY" ,
388
+ {
389
+ pollIntervalSeconds,
390
+ setupHooks : hooks => {
391
+ hooks . on ( "clientReady" , cacheState => clientReadyEvents . push ( cacheState ) ) ;
392
+ hooks . on ( "configChanged" , config => configChangedEvents . push ( config ) ) ;
393
+ } ,
394
+ } ,
395
+ createKernel ( { defaultCacheFactory : ( ) => cache } )
396
+ ) ;
397
+
398
+ const fr : FetchResult = createFetchResult ( ) ;
399
+ const fetcherMock = new Mock < IConfigFetcher > ( )
400
+ . setup ( m => m . fetchLogic ( It . IsAny < OptionsBase > ( ) , It . IsAny < string > ( ) ) )
401
+ . returnsAsync ( { statusCode : 200 , reasonPhrase : "OK" , eTag : fr . config . httpETag , body : fr . config . configJson } ) ;
402
+
403
+ // Act
404
+
405
+ const service : AutoPollConfigService = new AutoPollConfigService (
406
+ fetcherMock . object ( ) ,
407
+ options ) ;
408
+
409
+ assert . isUndefined ( fakeExternalCache . cachedValue ) ;
410
+
411
+ assert . isEmpty ( clientReadyEvents ) ;
412
+ assert . isEmpty ( configChangedEvents ) ;
413
+
414
+ await service . readyPromise ;
415
+
416
+ const getConfigPromise = service . getConfig ( ) ;
417
+ await service . getConfig ( ) ; // simulate concurrent cache sync up
418
+ await getConfigPromise ;
419
+
420
+ await delay ( 100 ) ; // allow a little time for the client to raise ConfigChanged
421
+
422
+ assert . isDefined ( fakeExternalCache . cachedValue ) ;
423
+
424
+ assert . strictEqual ( 1 , clientReadyEvents . length ) ;
425
+ assert . strictEqual ( ClientCacheState . HasUpToDateFlagData , clientReadyEvents [ 0 ] ) ;
426
+ assert . strictEqual ( 1 , configChangedEvents . length ) ;
427
+ assert . strictEqual ( JSON . stringify ( fr . config . config ) , JSON . stringify ( configChangedEvents [ 0 ] ) ) ;
428
+
429
+ fetcherMock . verify ( m => m . fetchLogic ( It . IsAny < OptionsBase > ( ) , It . IsAny < string > ( ) ) , Times . Once ( ) ) ;
430
+
431
+ service . setOffline ( ) ; // no HTTP fetching from this point on
432
+
433
+ await delay ( pollIntervalSeconds * 1000 + 50 ) ;
434
+
435
+ assert . strictEqual ( 1 , clientReadyEvents . length ) ;
436
+ assert . strictEqual ( 1 , configChangedEvents . length ) ;
437
+
438
+ const fr2 : FetchResult = createFetchResult ( "etag2" , "{}" ) ;
439
+ const projectConfig2 = createConfigFromFetchResult ( fr2 ) ;
440
+ fakeExternalCache . cachedValue = ProjectConfig . serialize ( projectConfig2 ) ;
441
+
442
+ const [ refreshResult , projectConfigFromRefresh ] = await service . refreshConfigAsync ( ) ;
443
+
444
+ // Assert
445
+
446
+ assert . isTrue ( refreshResult . isSuccess ) ;
447
+
448
+ assert . strictEqual ( 1 , clientReadyEvents . length ) ;
449
+ assert . strictEqual ( 2 , configChangedEvents . length ) ;
450
+ assert . strictEqual ( JSON . stringify ( fr2 . config . config ) , JSON . stringify ( configChangedEvents [ 1 ] ) ) ;
451
+ assert . strictEqual ( configChangedEvents [ 1 ] , projectConfigFromRefresh . config ) ;
452
+
453
+ // Cleanup
454
+
455
+ service . dispose ( ) ;
456
+ } ) ;
457
+ }
458
+
372
459
it ( "LazyLoadConfigService - ProjectConfig is different in the cache - should fetch a new config and put into cache" , async ( ) => {
373
460
374
461
// Arrange
@@ -836,17 +923,19 @@ describe("ConfigServiceBaseTests", () => {
836
923
} ) ;
837
924
} ) ;
838
925
839
- function createProjectConfig ( eTag = "etag" ) : ProjectConfig {
840
- const configJson = "{\"f\": { \"debug\": { \"v\": { \"b\": true }, \"i\": \"abcdefgh\", \"t\": 0, \"p\": [], \"r\": [] } } }" ;
926
+ const DEFAULT_ETAG = "etag" ;
927
+ const DEFAULT_CONFIG_JSON = '{"f": { "debug": { "v": { "b": true }, "i": "abcdefgh", "t": 0, "p": [], "r": [] } } }' ;
928
+
929
+ function createProjectConfig ( eTag = DEFAULT_ETAG , configJson = DEFAULT_CONFIG_JSON ) : ProjectConfig {
841
930
return new ProjectConfig (
842
931
configJson ,
843
932
Config . deserialize ( configJson ) ,
844
933
1 ,
845
934
eTag ) ;
846
935
}
847
936
848
- function createFetchResult ( eTag = "etag" ) : FetchResult {
849
- return FetchResult . success ( createProjectConfig ( eTag ) ) ;
937
+ function createFetchResult ( eTag = DEFAULT_ETAG , configJson = DEFAULT_CONFIG_JSON ) : FetchResult {
938
+ return FetchResult . success ( createProjectConfig ( eTag , configJson ) ) ;
850
939
}
851
940
852
941
function createConfigFromFetchResult ( result : FetchResult ) : ProjectConfig {
0 commit comments