@@ -30,7 +30,9 @@ import {
30
30
validateTestAssignments ,
31
31
} from '../test/testHelpers' ;
32
32
33
- import { getInstance , IAssignmentEvent , IAssignmentLogger , init } from '.' ;
33
+ import * as util from './util/index' ;
34
+
35
+ import { getInstance , IAssignmentEvent , IAssignmentLogger , init , NO_OP_EVENT_DISPATCHER } from '.' ;
34
36
35
37
import SpyInstance = jest . SpyInstance ;
36
38
@@ -367,22 +369,34 @@ describe('EppoClient E2E test', () => {
367
369
describe ( 'Bandit assignment cache' , ( ) => {
368
370
const flagKey = 'banner_bandit_flag' ; // piggyback off a shared test data flag
369
371
const bobKey = 'bob' ;
370
- const bobAttributes : Attributes = { age : 25 , country : 'USA' , gender_identity : 'female' } ;
372
+ const bobAttributes : Attributes = {
373
+ age : 25 ,
374
+ country : 'USA' ,
375
+ gender_identity : 'female' ,
376
+ } ;
371
377
const bobActions : Record < string , Attributes > = {
372
378
nike : { brand_affinity : 1.5 , loyalty_tier : 'silver' } ,
373
379
adidas : { brand_affinity : - 1.0 , loyalty_tier : 'bronze' } ,
374
380
reebok : { brand_affinity : 0.5 , loyalty_tier : 'gold' } ,
375
381
} ;
376
382
377
383
const aliceKey = 'alice' ;
378
- const aliceAttributes : Attributes = { age : 25 , country : 'USA' , gender_identity : 'female' } ;
384
+ const aliceAttributes : Attributes = {
385
+ age : 25 ,
386
+ country : 'USA' ,
387
+ gender_identity : 'female' ,
388
+ } ;
379
389
const aliceActions : Record < string , Attributes > = {
380
390
nike : { brand_affinity : 1.5 , loyalty_tier : 'silver' } ,
381
391
adidas : { brand_affinity : - 1.0 , loyalty_tier : 'bronze' } ,
382
392
reebok : { brand_affinity : 0.5 , loyalty_tier : 'gold' } ,
383
393
} ;
384
394
const charlieKey = 'charlie' ;
385
- const charlieAttributes : Attributes = { age : 25 , country : 'USA' , gender_identity : 'female' } ;
395
+ const charlieAttributes : Attributes = {
396
+ age : 25 ,
397
+ country : 'USA' ,
398
+ gender_identity : 'female' ,
399
+ } ;
386
400
const charlieActions : Record < string , Attributes > = {
387
401
nike : { brand_affinity : 1.0 , loyalty_tier : 'gold' } ,
388
402
adidas : { brand_affinity : 1.0 , loyalty_tier : 'silver' } ,
@@ -452,7 +466,11 @@ describe('EppoClient E2E test', () => {
452
466
453
467
describe ( 'Best Bandit Action' , ( ) => {
454
468
const flagKey = 'banner_bandit_flag' ; // piggyback off a shared test data flag
455
- const bobAttributes : Attributes = { age : 25 , country : 'USA' , gender_identity : 'female' } ;
469
+ const bobAttributes : Attributes = {
470
+ age : 25 ,
471
+ country : 'USA' ,
472
+ gender_identity : 'female' ,
473
+ } ;
456
474
const bobActions : Record < string , Attributes > = {
457
475
nike : { brand_affinity : - 10.5 , loyalty_tier : 'silver' } ,
458
476
adidas : { brand_affinity : 1.0 , loyalty_tier : 'bronze' } ,
@@ -469,7 +487,6 @@ describe('EppoClient E2E test', () => {
469
487
470
488
it ( 'Should return the highest ranked bandit' , async ( ) => {
471
489
const client = getInstance ( ) ;
472
-
473
490
const bestAction = client . getBestAction ( flagKey , bobAttributes , bobActions , 'default' ) ;
474
491
475
492
expect ( bestAction ) . toEqual ( 'adidas' ) ;
@@ -590,4 +607,108 @@ describe('EppoClient E2E test', () => {
590
607
expect ( client . getStringAssignment ( flagKey , 'subject' , { } , 'default-value' ) ) . toBe ( 'control' ) ;
591
608
} ) ;
592
609
} ) ;
610
+
611
+ describe ( 'eventIngestionConfig' , ( ) => {
612
+ it ( 'should not be used if eventIngestionConfig.disabled is true' , async ( ) => {
613
+ const client = await init ( {
614
+ apiKey,
615
+ baseUrl : `http://127.0.0.1:${ TEST_SERVER_PORT } ` ,
616
+ assignmentLogger : mockLogger ,
617
+ eventIngestionConfig : {
618
+ disabled : true ,
619
+ } ,
620
+ } ) ;
621
+ expect ( client [ 'eventDispatcher' ] ) . toEqual ( NO_OP_EVENT_DISPATCHER ) ;
622
+ } ) ;
623
+
624
+ it ( 'should be used if eventIngestionConfig.disabled is false' , async ( ) => {
625
+ const client = await init ( {
626
+ apiKey,
627
+ baseUrl : `http://127.0.0.1:${ TEST_SERVER_PORT } ` ,
628
+ assignmentLogger : mockLogger ,
629
+ eventIngestionConfig : {
630
+ disabled : false ,
631
+ } ,
632
+ } ) ;
633
+ expect ( client [ 'eventDispatcher' ] ) . not . toEqual ( NO_OP_EVENT_DISPATCHER ) ;
634
+ } ) ;
635
+
636
+ describe ( 'read-only file system handling' , ( ) => {
637
+ // Save original implementation
638
+ let isReadOnlyFsSpy : SpyInstance ;
639
+
640
+ beforeEach ( ( ) => {
641
+ // Reset the module before each test
642
+ jest . resetModules ( ) ;
643
+ // Create a spy on isReadOnlyFs that we can mock
644
+ isReadOnlyFsSpy = jest . spyOn ( util , 'isReadOnlyFs' ) ;
645
+ } ) ;
646
+
647
+ afterEach ( ( ) => {
648
+ isReadOnlyFsSpy . mockRestore ( ) ;
649
+ } ) ;
650
+
651
+ it ( 'should use BoundedEventQueue when file system is read-only' , async ( ) => {
652
+ // Mock isReadOnlyFs to return true (read-only file system)
653
+ isReadOnlyFsSpy . mockReturnValue ( true ) ;
654
+ const client = await init ( {
655
+ apiKey,
656
+ baseUrl : `http://127.0.0.1:${ TEST_SERVER_PORT } ` ,
657
+ assignmentLogger : mockLogger ,
658
+ eventIngestionConfig : {
659
+ disabled : false ,
660
+ } ,
661
+ } ) ;
662
+
663
+ // Check that the event queue is a BoundedEventQueue
664
+ // We can't directly check the type, but we can check that it's not a FileBackedNamedEventQueue
665
+ // by checking if the queue has a 'queueDirectory' property
666
+ const eventDispatcher = client [ 'eventDispatcher' ] ;
667
+ const eventQueue = eventDispatcher [ 'batchProcessor' ] [ 'eventQueue' ] ;
668
+ expect ( eventQueue ) . toBeDefined ( ) ;
669
+ expect ( eventQueue [ 'queueDirectory' ] ) . toBeUndefined ( ) ;
670
+ } ) ;
671
+
672
+ it ( 'should use FileBackedNamedEventQueue when file system is writable' , async ( ) => {
673
+ // Mock isReadOnlyFs to return false (writable file system)
674
+ isReadOnlyFsSpy . mockReturnValue ( false ) ;
675
+ const client = await init ( {
676
+ apiKey,
677
+ baseUrl : `http://127.0.0.1:${ TEST_SERVER_PORT } ` ,
678
+ assignmentLogger : mockLogger ,
679
+ eventIngestionConfig : {
680
+ disabled : false ,
681
+ } ,
682
+ } ) ;
683
+
684
+ // Check that the event queue is a FileBackedNamedEventQueue
685
+ // by checking if the queue has a 'queueDirectory' property
686
+ const eventDispatcher = client [ 'eventDispatcher' ] ;
687
+ const eventQueue = eventDispatcher [ 'batchProcessor' ] [ 'eventQueue' ] ;
688
+ expect ( eventQueue ) . toBeDefined ( ) ;
689
+ expect ( eventQueue [ 'queueDirectory' ] ) . toBeDefined ( ) ;
690
+ } ) ;
691
+
692
+ it ( 'should use BoundedEventQueue when isReadOnlyFs throws an error' , async ( ) => {
693
+ // Mock isReadOnlyFs to throw an error
694
+ isReadOnlyFsSpy . mockImplementation ( ( ) => {
695
+ throw new Error ( 'Test error' ) ;
696
+ } ) ;
697
+ const client = await init ( {
698
+ apiKey,
699
+ baseUrl : `http://127.0.0.1:${ TEST_SERVER_PORT } ` ,
700
+ assignmentLogger : mockLogger ,
701
+ eventIngestionConfig : {
702
+ disabled : false ,
703
+ } ,
704
+ } ) ;
705
+
706
+ // Check that the event queue is a BoundedEventQueue
707
+ const eventDispatcher = client [ 'eventDispatcher' ] ;
708
+ const eventQueue = eventDispatcher [ 'batchProcessor' ] [ 'eventQueue' ] ;
709
+ expect ( eventQueue ) . toBeDefined ( ) ;
710
+ expect ( eventQueue [ 'queueDirectory' ] ) . toBeUndefined ( ) ;
711
+ } ) ;
712
+ } ) ;
713
+ } ) ;
593
714
} ) ;
0 commit comments