@@ -28,6 +28,7 @@ describe('FlagPersistence tests', () => {
2828 makeMockPlatform ( makeMemoryStorage ( ) , makeMockCrypto ( ) ) ,
2929 TEST_NAMESPACE ,
3030 5 ,
31+ false ,
3132 flagStore ,
3233 createFlagUpdater ( flagStore , mockLogger ) ,
3334 mockLogger ,
@@ -45,6 +46,7 @@ describe('FlagPersistence tests', () => {
4546 makeMockPlatform ( makeCorruptStorage ( ) , makeMockCrypto ( ) ) ,
4647 TEST_NAMESPACE ,
4748 5 ,
49+ false ,
4850 flagStore ,
4951 createFlagUpdater ( flagStore , mockLogger ) ,
5052 mockLogger ,
@@ -72,6 +74,7 @@ describe('FlagPersistence tests', () => {
7274 makeMockPlatform ( makeMemoryStorage ( ) , makeMockCrypto ( ) ) ,
7375 TEST_NAMESPACE ,
7476 5 ,
77+ false ,
7578 flagStore ,
7679 flagUpdater ,
7780 mockLogger ,
@@ -100,6 +103,7 @@ describe('FlagPersistence tests', () => {
100103 makeMockPlatform ( memoryStorage , makeMockCrypto ( ) ) ,
101104 TEST_NAMESPACE ,
102105 5 ,
106+ false ,
103107 flagStore ,
104108 flagUpdater ,
105109 mockLogger ,
@@ -129,6 +133,7 @@ describe('FlagPersistence tests', () => {
129133 mockPlatform ,
130134 TEST_NAMESPACE ,
131135 5 ,
136+ false ,
132137 flagStore ,
133138 flagUpdater ,
134139 mockLogger ,
@@ -165,6 +170,7 @@ describe('FlagPersistence tests', () => {
165170 mockPlatform ,
166171 TEST_NAMESPACE ,
167172 1 ,
173+ false ,
168174 flagStore ,
169175 flagUpdater ,
170176 mockLogger ,
@@ -213,6 +219,7 @@ describe('FlagPersistence tests', () => {
213219 mockPlatform ,
214220 TEST_NAMESPACE ,
215221 1 ,
222+ false ,
216223 flagStore ,
217224 flagUpdater ,
218225 mockLogger ,
@@ -244,6 +251,7 @@ describe('FlagPersistence tests', () => {
244251 mockPlatform ,
245252 TEST_NAMESPACE ,
246253 5 ,
254+ false ,
247255 flagStore ,
248256 flagUpdater ,
249257 mockLogger ,
@@ -277,6 +285,7 @@ describe('FlagPersistence tests', () => {
277285 mockPlatform ,
278286 TEST_NAMESPACE ,
279287 5 ,
288+ false ,
280289 flagStore ,
281290 flagUpdater ,
282291 mockLogger ,
@@ -305,6 +314,165 @@ describe('FlagPersistence tests', () => {
305314 expect ( await memoryStorage . get ( contextDataKey ) ) . toContain ( '"version":2' ) ;
306315 } ) ;
307316
317+ it ( 'does not write to storage when maxCachedContexts is 0' , async ( ) => {
318+ const memoryStorage = makeMemoryStorage ( ) ;
319+ const mockPlatform = makeMockPlatform ( memoryStorage , makeMockCrypto ( ) ) ;
320+ const flagStore = createDefaultFlagStore ( ) ;
321+ const mockLogger = makeMockLogger ( ) ;
322+ const flagUpdater = createFlagUpdater ( flagStore , mockLogger ) ;
323+
324+ const fpUnderTest = new FlagPersistence (
325+ mockPlatform ,
326+ TEST_NAMESPACE ,
327+ 0 ,
328+ false ,
329+ flagStore ,
330+ flagUpdater ,
331+ mockLogger ,
332+ ) ;
333+
334+ const context = Context . fromLDContext ( { kind : 'org' , key : 'TestyPizza' } ) ;
335+ const flags = { flagA : { version : 1 , flag : makeMockFlag ( ) } } ;
336+
337+ await fpUnderTest . init ( context , flags ) ;
338+
339+ const contextDataKey = await namespaceForContextData ( mockPlatform . crypto , TEST_NAMESPACE , context ) ;
340+ expect ( await memoryStorage . get ( contextDataKey ) ) . toBeNull ( ) ;
341+ } ) ;
342+
343+ it ( 'clears previously cached data when maxCachedContexts is 0' , async ( ) => {
344+ const memoryStorage = makeMemoryStorage ( ) ;
345+ const crypto = makeMockCrypto ( ) ;
346+ const mockPlatform = makeMockPlatform ( memoryStorage , crypto ) ;
347+ const flagStore = createDefaultFlagStore ( ) ;
348+ const mockLogger = makeMockLogger ( ) ;
349+ const flagUpdater = createFlagUpdater ( flagStore , mockLogger ) ;
350+
351+ const contextA = Context . fromLDContext ( { kind : 'org' , key : 'TestyPizza' } ) ;
352+ const storageKeyA = await namespaceForContextData ( crypto , TEST_NAMESPACE , contextA ) ;
353+ const indexKey = await namespaceForContextIndex ( TEST_NAMESPACE ) ;
354+
355+ // Pre-populate storage as if a prior session had maxCachedContexts > 0
356+ const indexJson = JSON . stringify ( { index : [ { id : storageKeyA , timestamp : 1 } ] } ) ;
357+ await memoryStorage . set ( indexKey , indexJson ) ;
358+ await memoryStorage . set ( storageKeyA , JSON . stringify ( { flagA : makeMockFlag ( ) } ) ) ;
359+
360+ const fpUnderTest = new FlagPersistence (
361+ mockPlatform ,
362+ TEST_NAMESPACE ,
363+ 0 ,
364+ false ,
365+ flagStore ,
366+ flagUpdater ,
367+ mockLogger ,
368+ ) ;
369+
370+ const flags = { flagA : { version : 1 , flag : makeMockFlag ( ) } } ;
371+ await fpUnderTest . init ( contextA , flags ) ;
372+
373+ // Existing entry must have been evicted
374+ expect ( await memoryStorage . get ( storageKeyA ) ) . toBeNull ( ) ;
375+ // Index must be saved as empty
376+ const savedIndex = JSON . parse ( ( await memoryStorage . get ( indexKey ) ) ! ) ;
377+ expect ( savedIndex . index ) . toHaveLength ( 0 ) ;
378+ } ) ;
379+
380+ it ( 'does not load from storage when maxCachedContexts is 0' , async ( ) => {
381+ const memoryStorage = makeMemoryStorage ( ) ;
382+ const mockPlatform = makeMockPlatform ( memoryStorage , makeMockCrypto ( ) ) ;
383+ const flagStore = createDefaultFlagStore ( ) ;
384+ const mockLogger = makeMockLogger ( ) ;
385+ const flagUpdater = createFlagUpdater ( flagStore , mockLogger ) ;
386+
387+ // First write data to storage using a normal FlagPersistence
388+ const writeFp = new FlagPersistence (
389+ mockPlatform ,
390+ TEST_NAMESPACE ,
391+ 5 ,
392+ false ,
393+ flagStore ,
394+ flagUpdater ,
395+ mockLogger ,
396+ ) ;
397+ const context = Context . fromLDContext ( { kind : 'org' , key : 'TestyPizza' } ) ;
398+ const flags = { flagA : { version : 1 , flag : makeMockFlag ( ) } } ;
399+ await writeFp . init ( context , flags ) ;
400+
401+ // Now try to load with maxCachedContexts: 0
402+ const fpUnderTest = new FlagPersistence (
403+ mockPlatform ,
404+ TEST_NAMESPACE ,
405+ 0 ,
406+ false ,
407+ flagStore ,
408+ flagUpdater ,
409+ mockLogger ,
410+ ) ;
411+ const didLoad = await fpUnderTest . loadCached ( context ) ;
412+ expect ( didLoad ) . toEqual ( false ) ;
413+ } ) ;
414+
415+ it ( 'does not write to storage when disableCache is true' , async ( ) => {
416+ const memoryStorage = makeMemoryStorage ( ) ;
417+ const mockPlatform = makeMockPlatform ( memoryStorage , makeMockCrypto ( ) ) ;
418+ const flagStore = createDefaultFlagStore ( ) ;
419+ const mockLogger = makeMockLogger ( ) ;
420+ const flagUpdater = createFlagUpdater ( flagStore , mockLogger ) ;
421+
422+ const fpUnderTest = new FlagPersistence (
423+ mockPlatform ,
424+ TEST_NAMESPACE ,
425+ 5 ,
426+ true ,
427+ flagStore ,
428+ flagUpdater ,
429+ mockLogger ,
430+ ) ;
431+
432+ const context = Context . fromLDContext ( { kind : 'org' , key : 'TestyPizza' } ) ;
433+ const flags = { flagA : { version : 1 , flag : makeMockFlag ( ) } } ;
434+
435+ await fpUnderTest . init ( context , flags ) ;
436+
437+ const contextDataKey = await namespaceForContextData ( mockPlatform . crypto , TEST_NAMESPACE , context ) ;
438+ expect ( await memoryStorage . get ( contextDataKey ) ) . toBeNull ( ) ;
439+ } ) ;
440+
441+ it ( 'does not load from storage when disableCache is true' , async ( ) => {
442+ const memoryStorage = makeMemoryStorage ( ) ;
443+ const mockPlatform = makeMockPlatform ( memoryStorage , makeMockCrypto ( ) ) ;
444+ const flagStore = createDefaultFlagStore ( ) ;
445+ const mockLogger = makeMockLogger ( ) ;
446+ const flagUpdater = createFlagUpdater ( flagStore , mockLogger ) ;
447+
448+ // First write data to storage using a normal FlagPersistence
449+ const writeFp = new FlagPersistence (
450+ mockPlatform ,
451+ TEST_NAMESPACE ,
452+ 5 ,
453+ false ,
454+ flagStore ,
455+ flagUpdater ,
456+ mockLogger ,
457+ ) ;
458+ const context = Context . fromLDContext ( { kind : 'org' , key : 'TestyPizza' } ) ;
459+ const flags = { flagA : { version : 1 , flag : makeMockFlag ( ) } } ;
460+ await writeFp . init ( context , flags ) ;
461+
462+ // Now try to load with disableCache: true
463+ const fpUnderTest = new FlagPersistence (
464+ mockPlatform ,
465+ TEST_NAMESPACE ,
466+ 5 ,
467+ true ,
468+ flagStore ,
469+ flagUpdater ,
470+ mockLogger ,
471+ ) ;
472+ const didLoad = await fpUnderTest . loadCached ( context ) ;
473+ expect ( didLoad ) . toEqual ( false ) ;
474+ } ) ;
475+
308476 test ( 'upsert ignores inactive context' , async ( ) => {
309477 const memoryStorage = makeMemoryStorage ( ) ;
310478 const mockPlatform = makeMockPlatform ( memoryStorage , makeMockCrypto ( ) ) ;
@@ -316,6 +484,7 @@ describe('FlagPersistence tests', () => {
316484 mockPlatform ,
317485 TEST_NAMESPACE ,
318486 5 ,
487+ false ,
319488 flagStore ,
320489 flagUpdater ,
321490 mockLogger ,
@@ -350,6 +519,55 @@ describe('FlagPersistence tests', () => {
350519 expect ( await memoryStorage . get ( activeContextDataKey ) ) . not . toBeNull ( ) ;
351520 expect ( await memoryStorage . get ( inactiveContextDataKey ) ) . toBeNull ( ) ;
352521 } ) ;
522+
523+ it ( 'does not write to storage when current context is pruned due to equal timestamps' , async ( ) => {
524+ const memoryStorage = makeMemoryStorage ( ) ;
525+ const crypto = makeMockCrypto ( ) ;
526+ const mockPlatform = makeMockPlatform ( memoryStorage , crypto ) ;
527+ const flagStore = createDefaultFlagStore ( ) ;
528+ const mockLogger = makeMockLogger ( ) ;
529+ const flagUpdater = createFlagUpdater ( flagStore , mockLogger ) ;
530+
531+ const contextA = Context . fromLDContext ( { kind : 'org' , key : 'TestyPizza' } ) ;
532+ const contextB = Context . fromLDContext ( { kind : 'user' , key : 'TestyUser' } ) ;
533+
534+ const storageKeyA = await namespaceForContextData ( crypto , TEST_NAMESPACE , contextA ) ;
535+ const storageKeyB = await namespaceForContextData ( crypto , TEST_NAMESPACE , contextB ) ;
536+ const indexKey = await namespaceForContextIndex ( TEST_NAMESPACE ) ;
537+
538+ // Pre-populate storage: index with A before B (same timestamp t=1), and B's flag data
539+ const indexJson = JSON . stringify ( {
540+ index : [
541+ { id : storageKeyA , timestamp : 1 } ,
542+ { id : storageKeyB , timestamp : 1 } ,
543+ ] ,
544+ } ) ;
545+ await memoryStorage . set ( indexKey , indexJson ) ;
546+ await memoryStorage . set ( storageKeyB , JSON . stringify ( { flagB : makeMockFlag ( ) } ) ) ;
547+
548+ const fpUnderTest = new FlagPersistence (
549+ mockPlatform ,
550+ TEST_NAMESPACE ,
551+ 1 ,
552+ false ,
553+ flagStore ,
554+ flagUpdater ,
555+ mockLogger ,
556+ ( ) => 1 ,
557+ ) ;
558+
559+ const flags = { flagA : { version : 1 , flag : makeMockFlag ( ) } } ;
560+ await fpUnderTest . init ( contextA , flags ) ;
561+
562+ // A was in the pruned list — must not be re-written to storage
563+ expect ( await memoryStorage . get ( storageKeyA ) ) . toBeNull ( ) ;
564+ // B was not pruned — its existing data should be untouched
565+ expect ( await memoryStorage . get ( storageKeyB ) ) . not . toBeNull ( ) ;
566+ // Index should contain only B
567+ const savedIndex = JSON . parse ( ( await memoryStorage . get ( indexKey ) ) ! ) ;
568+ expect ( savedIndex . index ) . toHaveLength ( 1 ) ;
569+ expect ( savedIndex . index [ 0 ] . id ) . toBe ( storageKeyB ) ;
570+ } ) ;
353571} ) ;
354572
355573describe ( 'FlagPersistence freshness' , ( ) => {
@@ -363,6 +581,7 @@ describe('FlagPersistence freshness', () => {
363581 makeMockPlatform ( memoryStorage , crypto ) ,
364582 TEST_NAMESPACE ,
365583 5 ,
584+ false ,
366585 flagStore ,
367586 createFlagUpdater ( flagStore , mockLogger ) ,
368587 mockLogger ,
0 commit comments