@@ -23,7 +23,7 @@ import { IConfigurationStore } from '../configuration-store/configuration-store'
23
23
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store' ;
24
24
import { MAX_EVENT_QUEUE_SIZE , DEFAULT_POLL_INTERVAL_MS , POLL_JITTER_PCT } from '../constants' ;
25
25
import { decodePrecomputedFlag } from '../decoding' ;
26
- import { Flag , ObfuscatedFlag , VariationType } from '../interfaces' ;
26
+ import { Flag , ObfuscatedFlag , Variation , VariationType } from '../interfaces' ;
27
27
import { getMD5Hash } from '../obfuscation' ;
28
28
import { AttributeType } from '../types' ;
29
29
@@ -945,4 +945,146 @@ describe('EppoClient E2E test', () => {
945
945
) ;
946
946
} ) ;
947
947
} ) ;
948
+
949
+ describe ( 'flag overrides' , ( ) => {
950
+ let client : EppoClient ;
951
+ let mockLogger : IAssignmentLogger ;
952
+ let overrideStore : IConfigurationStore < Variation > ;
953
+
954
+ beforeEach ( ( ) => {
955
+ storage . setEntries ( { [ flagKey ] : mockFlag } ) ;
956
+ mockLogger = td . object < IAssignmentLogger > ( ) ;
957
+ overrideStore = new MemoryOnlyConfigurationStore < Variation > ( ) ;
958
+ client = new EppoClient ( {
959
+ flagConfigurationStore : storage ,
960
+ overridesStore : overrideStore ,
961
+ } ) ;
962
+ client . setAssignmentLogger ( mockLogger ) ;
963
+ client . useNonExpiringInMemoryAssignmentCache ( ) ;
964
+ } ) ;
965
+
966
+ it ( 'returns override values for all supported types' , ( ) => {
967
+ overrideStore . setEntries ( {
968
+ 'string-flag' : {
969
+ key : 'override-variation' ,
970
+ value : 'override-string' ,
971
+ } ,
972
+ 'boolean-flag' : {
973
+ key : 'override-variation' ,
974
+ value : true ,
975
+ } ,
976
+ 'numeric-flag' : {
977
+ key : 'override-variation' ,
978
+ value : 42.5 ,
979
+ } ,
980
+ 'json-flag' : {
981
+ key : 'override-variation' ,
982
+ value : '{"foo": "bar"}' ,
983
+ } ,
984
+ } ) ;
985
+
986
+ expect ( client . getStringAssignment ( 'string-flag' , 'subject-10' , { } , 'default' ) ) . toBe (
987
+ 'override-string' ,
988
+ ) ;
989
+ expect ( client . getBooleanAssignment ( 'boolean-flag' , 'subject-10' , { } , false ) ) . toBe ( true ) ;
990
+ expect ( client . getNumericAssignment ( 'numeric-flag' , 'subject-10' , { } , 0 ) ) . toBe ( 42.5 ) ;
991
+ expect ( client . getJSONAssignment ( 'json-flag' , 'subject-10' , { } , { } ) ) . toEqual ( { foo : 'bar' } ) ;
992
+ } ) ;
993
+
994
+ it ( 'does not log assignments when override is applied' , ( ) => {
995
+ overrideStore . setEntries ( {
996
+ [ flagKey ] : {
997
+ key : 'override-variation' ,
998
+ value : 'override-value' ,
999
+ } ,
1000
+ } ) ;
1001
+
1002
+ client . getStringAssignment ( flagKey , 'subject-10' , { } , 'default' ) ;
1003
+
1004
+ expect ( td . explain ( mockLogger . logAssignment ) . callCount ) . toBe ( 0 ) ;
1005
+ } ) ;
1006
+
1007
+ it ( 'includes override details in assignment details' , ( ) => {
1008
+ overrideStore . setEntries ( {
1009
+ [ flagKey ] : {
1010
+ key : 'override-variation' ,
1011
+ value : 'override-value' ,
1012
+ } ,
1013
+ } ) ;
1014
+
1015
+ const result = client . getStringAssignmentDetails (
1016
+ flagKey ,
1017
+ 'subject-10' ,
1018
+ { foo : 3 } ,
1019
+ 'default' ,
1020
+ ) ;
1021
+
1022
+ expect ( result ) . toMatchObject ( {
1023
+ variation : 'override-value' ,
1024
+ evaluationDetails : {
1025
+ flagEvaluationCode : 'MATCH' ,
1026
+ flagEvaluationDescription : 'Flag override applied' ,
1027
+ } ,
1028
+ } ) ;
1029
+ } ) ;
1030
+
1031
+ it ( 'does not update assignment cache when override is applied' , ( ) => {
1032
+ overrideStore . setEntries ( {
1033
+ [ flagKey ] : {
1034
+ key : 'override-variation' ,
1035
+ value : 'override-value' ,
1036
+ } ,
1037
+ } ) ;
1038
+
1039
+ // First call with override
1040
+ client . getStringAssignment ( flagKey , 'subject-10' , { } , 'default' ) ;
1041
+
1042
+ // Remove override
1043
+ overrideStore . setEntries ( { } ) ;
1044
+
1045
+ // Second call without override should trigger logging since cache wasn't updated
1046
+ client . getStringAssignment ( flagKey , 'subject-10' , { } , 'default' ) ;
1047
+
1048
+ expect ( td . explain ( mockLogger . logAssignment ) . callCount ) . toBe ( 1 ) ;
1049
+ expect ( td . explain ( mockLogger . logAssignment ) . calls [ 0 ] . args [ 0 ] ) . toMatchObject ( {
1050
+ featureFlag : flagKey ,
1051
+ subject : 'subject-10' ,
1052
+ } ) ;
1053
+ } ) ;
1054
+
1055
+ it ( 'uses normal assignment when no override exists for flag' , ( ) => {
1056
+ // Set override for a different flag
1057
+ overrideStore . setEntries ( {
1058
+ 'other-flag' : {
1059
+ key : 'override-variation' ,
1060
+ value : 'override-value' ,
1061
+ } ,
1062
+ } ) ;
1063
+
1064
+ const result = client . getStringAssignment ( flagKey , 'subject-10' , { } , 'default' ) ;
1065
+
1066
+ // Should get the normal assignment value from mockFlag
1067
+ expect ( result ) . toBe ( variationA . value ) ;
1068
+ expect ( td . explain ( mockLogger . logAssignment ) . callCount ) . toBe ( 1 ) ;
1069
+ } ) ;
1070
+
1071
+ it ( 'uses normal assignment when no overrides store is configured' , ( ) => {
1072
+ // Create client without overrides store
1073
+ const clientWithoutOverrides = new EppoClient ( {
1074
+ flagConfigurationStore : storage ,
1075
+ } ) ;
1076
+ clientWithoutOverrides . setAssignmentLogger ( mockLogger ) ;
1077
+
1078
+ const result = clientWithoutOverrides . getStringAssignment (
1079
+ flagKey ,
1080
+ 'subject-10' ,
1081
+ { } ,
1082
+ 'default' ,
1083
+ ) ;
1084
+
1085
+ // Should get the normal assignment value from mockFlag
1086
+ expect ( result ) . toBe ( variationA . value ) ;
1087
+ expect ( td . explain ( mockLogger . logAssignment ) . callCount ) . toBe ( 1 ) ;
1088
+ } ) ;
1089
+ } ) ;
948
1090
} ) ;
0 commit comments