@@ -15,6 +15,7 @@ import {
15
15
} from '../../test/testHelpers' ;
16
16
import { IAssignmentLogger } from '../assignment-logger' ;
17
17
import { AssignmentCache } from '../cache/abstract-assignment-cache' ;
18
+ import { ConfigurationManager } from '../configuration-store/configuration-manager' ;
18
19
import { IConfigurationStore } from '../configuration-store/configuration-store' ;
19
20
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store' ;
20
21
import {
@@ -24,7 +25,15 @@ import {
24
25
} from '../configuration-wire/configuration-wire-types' ;
25
26
import { MAX_EVENT_QUEUE_SIZE , DEFAULT_POLL_INTERVAL_MS , POLL_JITTER_PCT } from '../constants' ;
26
27
import { decodePrecomputedFlag } from '../decoding' ;
27
- import { Flag , ObfuscatedFlag , VariationType , FormatEnum , Variation } from '../interfaces' ;
28
+ import {
29
+ Flag ,
30
+ ObfuscatedFlag ,
31
+ VariationType ,
32
+ FormatEnum ,
33
+ Variation ,
34
+ BanditVariation ,
35
+ BanditParameters ,
36
+ } from '../interfaces' ;
28
37
import { getMD5Hash } from '../obfuscation' ;
29
38
import { AttributeType } from '../types' ;
30
39
@@ -1191,3 +1200,237 @@ describe('EppoClient E2E test', () => {
1191
1200
} ) ;
1192
1201
} ) ;
1193
1202
} ) ;
1203
+
1204
+ describe ( 'EppoClient ConfigurationManager Integration' , ( ) => {
1205
+ let client : EppoClient ;
1206
+ let flagStore : MemoryOnlyConfigurationStore < Flag | ObfuscatedFlag > ;
1207
+ let banditVariationStore : MemoryOnlyConfigurationStore < BanditVariation [ ] > ;
1208
+ let banditModelStore : MemoryOnlyConfigurationStore < BanditParameters > ;
1209
+
1210
+ // Sample flag with correct shape
1211
+ const testFlag : Flag = {
1212
+ key : 'test-flag' ,
1213
+ enabled : true ,
1214
+ variationType : VariationType . STRING ,
1215
+ variations : {
1216
+ control : { key : 'control' , value : 'control-value' } ,
1217
+ treatment : { key : 'treatment' , value : 'treatment-value' } ,
1218
+ } ,
1219
+ allocations : [
1220
+ {
1221
+ key : 'allocation-1' ,
1222
+ rules : [ ] ,
1223
+ splits : [
1224
+ {
1225
+ shards : [ ] ,
1226
+ variationKey : 'treatment' ,
1227
+ } ,
1228
+ ] ,
1229
+ doLog : true ,
1230
+ } ,
1231
+ ] ,
1232
+ totalShards : 10000 ,
1233
+ } ;
1234
+
1235
+ // Sample bandit variation with correct shape
1236
+ const testBanditVariation : BanditVariation = {
1237
+ key : 'test-bandit' ,
1238
+ flagKey : 'test-flag' ,
1239
+ variationKey : 'treatment' ,
1240
+ variationValue : 'treatment-value' ,
1241
+ } ;
1242
+
1243
+ // Sample bandit parameters with correct shape
1244
+ const testBanditParameters : BanditParameters = {
1245
+ banditKey : 'test-bandit' ,
1246
+ modelName : 'test-model' ,
1247
+ modelVersion : '1.0' ,
1248
+ modelData : {
1249
+ gamma : 0 ,
1250
+ defaultActionScore : 0 ,
1251
+ actionProbabilityFloor : 0 ,
1252
+ coefficients : { } ,
1253
+ } ,
1254
+ } ;
1255
+
1256
+ beforeEach ( ( ) => {
1257
+ // Reset mocks
1258
+ jest . clearAllMocks ( ) ;
1259
+
1260
+ // Create fresh stores for each test
1261
+ flagStore = new MemoryOnlyConfigurationStore < Flag | ObfuscatedFlag > ( ) ;
1262
+ banditVariationStore = new MemoryOnlyConfigurationStore < BanditVariation [ ] > ( ) ;
1263
+ banditModelStore = new MemoryOnlyConfigurationStore < BanditParameters > ( ) ;
1264
+
1265
+ // Create client with the stores
1266
+ client = new EppoClient ( {
1267
+ flagConfigurationStore : flagStore ,
1268
+ banditVariationConfigurationStore : banditVariationStore ,
1269
+ banditModelConfigurationStore : banditModelStore ,
1270
+ } ) ;
1271
+ } ) ;
1272
+
1273
+ it ( 'should initialize ConfigurationManager in constructor' , ( ) => {
1274
+ // Access the private configurationManager field
1275
+ const configManager = ( client as any ) . configurationManager ;
1276
+
1277
+ expect ( configManager ) . toBeDefined ( ) ;
1278
+ expect ( configManager ) . toBeInstanceOf ( ConfigurationManager ) ;
1279
+ } ) ;
1280
+
1281
+ it ( 'should use ConfigurationManager for getConfiguration' , ( ) => {
1282
+ // Create a spy on the ConfigurationManager's getConfiguration method
1283
+ const configManager = ( client as any ) . configurationManager ;
1284
+ const getConfigSpy = jest . spyOn ( configManager , 'getConfiguration' ) ;
1285
+
1286
+ // Call the client's getConfiguration method
1287
+ const config = ( client as any ) . getConfiguration ( ) ;
1288
+
1289
+ // Verify the manager's method was called
1290
+ expect ( getConfigSpy ) . toHaveBeenCalled ( ) ;
1291
+ expect ( config ) . toBe ( configManager . getConfiguration ( ) ) ;
1292
+ } ) ;
1293
+
1294
+ it ( 'should update ConfigurationManager when setFlagConfigurationStore is called' , async ( ) => {
1295
+ // Create a new store
1296
+ const newFlagStore = new MemoryOnlyConfigurationStore < Flag | ObfuscatedFlag > ( ) ;
1297
+
1298
+ // Pre-populate with a test flag
1299
+ await newFlagStore . setEntries ( { 'test-flag' : testFlag } ) ;
1300
+ newFlagStore . setFormat ( FormatEnum . SERVER ) ;
1301
+
1302
+ // Create a spy on the ConfigurationManager's setConfigurationStores method
1303
+ const configManager = ( client as any ) . configurationManager ;
1304
+ const setStoresSpy = jest . spyOn ( configManager , 'setConfigurationStores' ) ;
1305
+
1306
+ // Call the setter method
1307
+ client . setFlagConfigurationStore ( newFlagStore ) ;
1308
+
1309
+ // Verify the manager's method was called with the correct arguments
1310
+ expect ( setStoresSpy ) . toHaveBeenCalledWith (
1311
+ expect . objectContaining ( {
1312
+ flagConfigurationStore : newFlagStore ,
1313
+ } ) ,
1314
+ ) ;
1315
+
1316
+ // Verify the configuration was updated by checking if we can access the flag
1317
+ const config = configManager . getConfiguration ( ) ;
1318
+ expect ( config . getFlag ( 'test-flag' ) ) . toEqual ( testFlag ) ;
1319
+ } ) ;
1320
+
1321
+ it ( 'should update ConfigurationManager when setBanditVariationConfigurationStore is called' , async ( ) => {
1322
+ // Create a new store
1323
+ const newBanditVariationStore = new MemoryOnlyConfigurationStore < BanditVariation [ ] > ( ) ;
1324
+
1325
+ // Pre-populate with test data
1326
+ await newBanditVariationStore . setEntries ( {
1327
+ 'test-flag' : [ testBanditVariation ] ,
1328
+ } ) ;
1329
+ newBanditVariationStore . setFormat ( FormatEnum . SERVER ) ;
1330
+
1331
+ // Create a spy on the ConfigurationManager's setConfigurationStores method
1332
+ const configManager = ( client as any ) . configurationManager ;
1333
+ const setStoresSpy = jest . spyOn ( configManager , 'setConfigurationStores' ) ;
1334
+
1335
+ // Call the setter method
1336
+ client . setBanditVariationConfigurationStore ( newBanditVariationStore ) ;
1337
+
1338
+ // Verify the manager's method was called with the correct arguments
1339
+ expect ( setStoresSpy ) . toHaveBeenCalledWith (
1340
+ expect . objectContaining ( {
1341
+ banditReferenceConfigurationStore : newBanditVariationStore ,
1342
+ } ) ,
1343
+ ) ;
1344
+
1345
+ // Verify the configuration was updated
1346
+ const config = configManager . getConfiguration ( ) ;
1347
+ expect ( config . getBanditVariations ( ) [ 'test-flag' ] ) . toEqual ( [ testBanditVariation ] ) ;
1348
+ } ) ;
1349
+
1350
+ it ( 'should update ConfigurationManager when setBanditModelConfigurationStore is called' , async ( ) => {
1351
+ // Create a new store
1352
+ const newBanditModelStore = new MemoryOnlyConfigurationStore < BanditParameters > ( ) ;
1353
+
1354
+ // Pre-populate with test data
1355
+ await newBanditModelStore . setEntries ( {
1356
+ 'test-bandit' : testBanditParameters ,
1357
+ } ) ;
1358
+ newBanditModelStore . setFormat ( FormatEnum . SERVER ) ;
1359
+
1360
+ // Create a spy on the ConfigurationManager's setConfigurationStores method
1361
+ const configManager = ( client as any ) . configurationManager ;
1362
+ const setStoresSpy = jest . spyOn ( configManager , 'setConfigurationStores' ) ;
1363
+
1364
+ // Call the setter method
1365
+ client . setBanditModelConfigurationStore ( newBanditModelStore ) ;
1366
+
1367
+ // Verify the manager's method was called with the correct arguments
1368
+ expect ( setStoresSpy ) . toHaveBeenCalledWith (
1369
+ expect . objectContaining ( {
1370
+ banditConfigurationStore : newBanditModelStore ,
1371
+ } ) ,
1372
+ ) ;
1373
+
1374
+ // Verify the configuration was updated
1375
+ const config = configManager . getConfiguration ( ) ;
1376
+ expect ( config . getBandits ( ) [ 'test-bandit' ] ) . toEqual ( testBanditParameters ) ;
1377
+ } ) ;
1378
+
1379
+ it ( 'should use configuration from ConfigurationManager for assignment decisions' , async ( ) => {
1380
+ // Create a new flag store with a test flag
1381
+ const newFlagStore = new MemoryOnlyConfigurationStore < Flag | ObfuscatedFlag > ( ) ;
1382
+ await newFlagStore . setEntries ( { 'test-flag' : testFlag } ) ;
1383
+ newFlagStore . setFormat ( FormatEnum . SERVER ) ;
1384
+
1385
+ // Update the client's flag store
1386
+ client . setFlagConfigurationStore ( newFlagStore ) ;
1387
+
1388
+ // Create a spy on the ConfigurationManager's getConfiguration method
1389
+ const configManager = ( client as any ) . configurationManager ;
1390
+ const getConfigSpy = jest . spyOn ( configManager , 'getConfiguration' ) ;
1391
+
1392
+ // Get an assignment
1393
+ const assignment = client . getStringAssignment ( 'test-flag' , 'subject-1' , { } , 'default' ) ;
1394
+
1395
+ // Verify the manager's getConfiguration method was called
1396
+ expect ( getConfigSpy ) . toHaveBeenCalled ( ) ;
1397
+
1398
+ // Verify we got the expected assignment (based on the test flag's configuration)
1399
+ expect ( assignment ) . toBe ( 'treatment-value' ) ;
1400
+ } ) ;
1401
+
1402
+ it ( 'should reflect changes in ConfigurationManager immediately in assignments' , async ( ) => {
1403
+ // First, set up a flag store with one variation
1404
+ const initialFlagStore = new MemoryOnlyConfigurationStore < Flag | ObfuscatedFlag > ( ) ;
1405
+ const initialFlag = { ...testFlag } ;
1406
+ await initialFlagStore . setEntries ( { 'test-flag' : initialFlag } ) ;
1407
+ initialFlagStore . setFormat ( FormatEnum . SERVER ) ;
1408
+
1409
+ client . setFlagConfigurationStore ( initialFlagStore ) ;
1410
+
1411
+ // Get initial assignment
1412
+ const initialAssignment = client . getStringAssignment ( 'test-flag' , 'subject-1' , { } , 'default' ) ;
1413
+ expect ( initialAssignment ) . toBe ( 'treatment-value' ) ;
1414
+
1415
+ // Now create a new flag store with a different variation
1416
+ const updatedFlagStore = new MemoryOnlyConfigurationStore < Flag | ObfuscatedFlag > ( ) ;
1417
+ const updatedFlag = {
1418
+ ...testFlag ,
1419
+ variations : {
1420
+ control : { key : 'control' , value : 'control-value' } ,
1421
+ treatment : { key : 'treatment' , value : 'new-treatment-value' } ,
1422
+ } ,
1423
+ } ;
1424
+ await updatedFlagStore . setEntries ( { 'test-flag' : updatedFlag } ) ;
1425
+ updatedFlagStore . setFormat ( FormatEnum . SERVER ) ;
1426
+
1427
+ // Update the client's flag store
1428
+ client . setFlagConfigurationStore ( updatedFlagStore ) ;
1429
+
1430
+ // Get updated assignment
1431
+ const updatedAssignment = client . getStringAssignment ( 'test-flag' , 'subject-1' , { } , 'default' ) ;
1432
+
1433
+ // Verify the assignment reflects the updated configuration
1434
+ expect ( updatedAssignment ) . toBe ( 'new-treatment-value' ) ;
1435
+ } ) ;
1436
+ } ) ;
0 commit comments