@@ -230,6 +230,77 @@ describe('ConfigurationRequestor', () => {
230
230
expect ( fetchSpy ) . toHaveBeenCalledTimes ( 3 ) ; // Once just for UFC, bandits should be skipped
231
231
} ) ;
232
232
233
+ const warmStartBanditReference = {
234
+ modelVersion : 'warm start' ,
235
+ flagVariations : [
236
+ {
237
+ key : 'warm_start_bandit' ,
238
+ flagKey : 'warm_start_bandit_flag' ,
239
+ variationKey : 'warm_start_bandit' ,
240
+ variationValue : 'warm_start_bandit' ,
241
+ } ,
242
+ ] ,
243
+ } ;
244
+
245
+ const warmStartBanditParameters = {
246
+ banditKey : 'warm_start_bandit' ,
247
+ modelName : 'pigeon' ,
248
+ modelVersion : 'warm start' ,
249
+ modelData : {
250
+ gamma : 1.0 ,
251
+ defaultActionScore : 0.0 ,
252
+ actionProbabilityFloor : 0.0 ,
253
+ coefficients : { } ,
254
+ } ,
255
+ } ;
256
+
257
+ const coldStartBanditParameters = {
258
+ banditKey : 'cold_start_bandit' ,
259
+ modelName : 'falcon' ,
260
+ modelVersion : 'cold start' ,
261
+ modelData : {
262
+ gamma : 1.0 ,
263
+ defaultActionScore : 0.0 ,
264
+ actionProbabilityFloor : 0.0 ,
265
+ coefficients : { } ,
266
+ } ,
267
+ } ;
268
+
269
+ function expectBanditToBeInModelStore (
270
+ store : IConfigurationStore < BanditParameters > ,
271
+ banditKey : string ,
272
+ expectedBanditParameters : BanditParameters ,
273
+ ) {
274
+ const bandit = store . get ( banditKey ) ;
275
+ expect ( bandit ) . toBeTruthy ( ) ;
276
+ expect ( bandit ?. banditKey ) . toBe ( expectedBanditParameters . banditKey ) ;
277
+ expect ( bandit ?. modelVersion ) . toBe ( expectedBanditParameters . modelVersion ) ;
278
+ expect ( bandit ?. modelName ) . toBe ( expectedBanditParameters . modelName ) ;
279
+ expect ( bandit ?. modelData . gamma ) . toBe ( expectedBanditParameters . modelData . gamma ) ;
280
+ expect ( bandit ?. modelData . defaultActionScore ) . toBe (
281
+ expectedBanditParameters . modelData . defaultActionScore ,
282
+ ) ;
283
+ expect ( bandit ?. modelData . actionProbabilityFloor ) . toBe (
284
+ expectedBanditParameters . modelData . actionProbabilityFloor ,
285
+ ) ;
286
+ expect ( bandit ?. modelData . coefficients ) . toStrictEqual (
287
+ expectedBanditParameters . modelData . coefficients ,
288
+ ) ;
289
+ }
290
+
291
+ function injectWarmStartBanditToResponseByUrl (
292
+ url : string ,
293
+ response : IUniversalFlagConfigResponse | IBanditParametersResponse ,
294
+ ) {
295
+ if ( url . includes ( 'config' ) && 'banditReferences' in response ) {
296
+ response . banditReferences . warm_start_bandit = warmStartBanditReference ;
297
+ }
298
+
299
+ if ( url . includes ( 'bandits' ) && 'bandits' in response ) {
300
+ response . bandits . warm_start_bandit = warmStartBanditParameters ;
301
+ }
302
+ }
303
+
233
304
it ( 'Should fetch bandits if new bandit references model versions appeared' , async ( ) => {
234
305
let updateUFC = false ;
235
306
await configurationRequestor . fetchAndStoreConfigurations ( ) ;
@@ -244,34 +315,7 @@ describe('ConfigurationRequestor', () => {
244
315
const response = readMockUFCResponse ( responseFile ) ;
245
316
246
317
if ( updateUFC === true ) {
247
- // this if is needed to appease linter
248
- if ( url . includes ( 'config' ) && 'banditReferences' in response ) {
249
- response . banditReferences . warm_start = {
250
- modelVersion : 'warm start' ,
251
- flagVariations : [
252
- {
253
- key : 'warm_start_bandit' ,
254
- flagKey : 'warm_start_bandit_flag' ,
255
- variationKey : 'warm_start_bandit' ,
256
- variationValue : 'warm_start_bandit' ,
257
- } ,
258
- ] ,
259
- } ;
260
- }
261
-
262
- if ( url . includes ( 'bandits' ) && 'bandits' in response ) {
263
- response . bandits . warm_start = {
264
- banditKey : 'warm_start_bandit' ,
265
- modelName : 'pigeon' ,
266
- modelVersion : 'warm start' ,
267
- modelData : {
268
- gamma : 1.0 ,
269
- defaultActionScore : 0.0 ,
270
- actionProbabilityFloor : 0.0 ,
271
- coefficients : { } ,
272
- } ,
273
- } ;
274
- }
318
+ injectWarmStartBanditToResponseByUrl ( url , response ) ;
275
319
}
276
320
return response ;
277
321
} ;
@@ -282,15 +326,11 @@ describe('ConfigurationRequestor', () => {
282
326
expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ; // 2 because fetchSpy was re-initiated, 1UFC and 1bandits
283
327
284
328
// let's check if warm start was hydrated properly!
285
- const warm_start_bandit = banditModelStore . get ( 'warm_start' ) ;
286
- expect ( warm_start_bandit ) . toBeTruthy ( ) ;
287
- expect ( warm_start_bandit ?. banditKey ) . toBe ( 'warm_start_bandit' ) ;
288
- expect ( warm_start_bandit ?. modelVersion ) . toBe ( 'warm start' ) ;
289
- expect ( warm_start_bandit ?. modelName ) . toBe ( 'pigeon' ) ;
290
- expect ( warm_start_bandit ?. modelData . gamma ) . toBe ( 1 ) ;
291
- expect ( warm_start_bandit ?. modelData . defaultActionScore ) . toBe ( 0 ) ;
292
- expect ( warm_start_bandit ?. modelData . actionProbabilityFloor ) . toBe ( 0 ) ;
293
- expect ( warm_start_bandit ?. modelData . coefficients ) . toStrictEqual ( { } ) ;
329
+ expectBanditToBeInModelStore (
330
+ banditModelStore ,
331
+ 'warm_start_bandit' ,
332
+ warmStartBanditParameters ,
333
+ ) ;
294
334
} ) ;
295
335
296
336
it ( 'Should not fetch bandits if bandit references model versions shrunk' , async ( ) => {
@@ -316,15 +356,67 @@ describe('ConfigurationRequestor', () => {
316
356
expect ( fetchSpy ) . toHaveBeenCalledTimes ( 1 ) ; // only once for UFC
317
357
318
358
// cold start should still be in memory
319
- const warm_start_bandit = banditModelStore . get ( 'cold_start_bandit' ) ;
320
- expect ( warm_start_bandit ) . toBeTruthy ( ) ;
321
- expect ( warm_start_bandit ?. banditKey ) . toBe ( 'cold_start_bandit' ) ;
322
- expect ( warm_start_bandit ?. modelVersion ) . toBe ( 'cold start' ) ;
323
- expect ( warm_start_bandit ?. modelName ) . toBe ( 'falcon' ) ;
324
- expect ( warm_start_bandit ?. modelData . gamma ) . toBe ( 1 ) ;
325
- expect ( warm_start_bandit ?. modelData . defaultActionScore ) . toBe ( 0 ) ;
326
- expect ( warm_start_bandit ?. modelData . actionProbabilityFloor ) . toBe ( 0 ) ;
327
- expect ( warm_start_bandit ?. modelData . coefficients ) . toStrictEqual ( { } ) ;
359
+ expectBanditToBeInModelStore (
360
+ banditModelStore ,
361
+ 'cold_start_bandit' ,
362
+ coldStartBanditParameters ,
363
+ ) ;
364
+ } ) ;
365
+
366
+ /**
367
+ * 1. initial call - 1 fetch for ufc 1 for bandits
368
+ * 2. 2nd call - 1 fetch for ufc only; bandits unchanged
369
+ * 3. 3rd call - new bandit ref injected to UFC; 2 fetches, because new bandit appeared
370
+ * 4. 4th call - we remove a bandit from ufc; 1 fetch because there is no need to update.
371
+ * The bandit removed from UFC should still be in memory.
372
+ **/
373
+ it ( 'should fetch bandits based on banditReference change in UFC' , async ( ) => {
374
+ let injectWarmStart = false ;
375
+ let removeColdStartBandit = false ;
376
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
377
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ;
378
+
379
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
380
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 3 ) ;
381
+
382
+ const customResponseMockGenerator = ( url : string ) => {
383
+ const responseFile = url . includes ( 'bandits' )
384
+ ? MOCK_BANDIT_MODELS_RESPONSE_FILE
385
+ : MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
386
+ const response = readMockUFCResponse ( responseFile ) ;
387
+ if ( injectWarmStart === true ) {
388
+ injectWarmStartBanditToResponseByUrl ( url , response ) ;
389
+ } else if (
390
+ removeColdStartBandit === true &&
391
+ 'banditReferences' in response &&
392
+ url . includes ( 'config' )
393
+ ) {
394
+ delete response . banditReferences . cold_start_bandit ;
395
+ }
396
+ return response ;
397
+ } ;
398
+ injectWarmStart = true ;
399
+ initiateFetchSpy ( customResponseMockGenerator ) ;
400
+
401
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
402
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ;
403
+ expectBanditToBeInModelStore (
404
+ banditModelStore ,
405
+ 'warm_start_bandit' ,
406
+ warmStartBanditParameters ,
407
+ ) ;
408
+
409
+ injectWarmStart = false ;
410
+ removeColdStartBandit = true ;
411
+ initiateFetchSpy ( customResponseMockGenerator ) ;
412
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
413
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 1 ) ;
414
+
415
+ expectBanditToBeInModelStore (
416
+ banditModelStore ,
417
+ 'cold_start_bandit' ,
418
+ coldStartBanditParameters ,
419
+ ) ;
328
420
} ) ;
329
421
} ) ;
330
422
} ) ;
0 commit comments