@@ -131,7 +131,7 @@ describe('ConfigurationRequestor', () => {
131
131
global . fetch = fetchSpy ;
132
132
}
133
133
134
- function responseMockGenerator ( url : string ) {
134
+ function defaultResponseMockGenerator ( url : string ) {
135
135
const responseFile = url . includes ( 'bandits' )
136
136
? MOCK_BANDIT_MODELS_RESPONSE_FILE
137
137
: MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
@@ -140,7 +140,7 @@ describe('ConfigurationRequestor', () => {
140
140
141
141
describe ( 'Fetching bandits' , ( ) => {
142
142
beforeAll ( ( ) => {
143
- initiateFetchSpy ( responseMockGenerator ) ;
143
+ initiateFetchSpy ( defaultResponseMockGenerator ) ;
144
144
} ) ;
145
145
146
146
it ( 'Fetches and populates bandit parameters' , async ( ) => {
@@ -230,193 +230,199 @@ 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
-
304
- it ( 'Should fetch bandits if new bandit references model versions appeared' , async ( ) => {
305
- let updateUFC = false ;
306
- await configurationRequestor . fetchAndStoreConfigurations ( ) ;
307
- await configurationRequestor . fetchAndStoreConfigurations ( ) ;
308
- expect ( fetchSpy ) . toHaveBeenCalledTimes ( 3 ) ;
309
-
310
- const customResponseMockGenerator = ( url : string ) => {
311
- const responseFile = url . includes ( 'bandits' )
312
- ? MOCK_BANDIT_MODELS_RESPONSE_FILE
313
- : MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
314
-
315
- const response = readMockUFCResponse ( responseFile ) ;
316
-
317
- if ( updateUFC === true ) {
318
- injectWarmStartBanditToResponseByUrl ( url , response ) ;
319
- }
320
- return response ;
233
+ describe ( 'Bandits polling' , ( ) => {
234
+ const warmStartBanditReference = {
235
+ modelVersion : 'warm start' ,
236
+ flagVariations : [
237
+ {
238
+ key : 'warm_start_bandit' ,
239
+ flagKey : 'warm_start_bandit_flag' ,
240
+ variationKey : 'warm_start_bandit' ,
241
+ variationValue : 'warm_start_bandit' ,
242
+ } ,
243
+ ] ,
321
244
} ;
322
- updateUFC = true ;
323
- initiateFetchSpy ( customResponseMockGenerator ) ;
324
245
325
- await configurationRequestor . fetchAndStoreConfigurations ( ) ;
326
- expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ; // 2 because fetchSpy was re-initiated, 1UFC and 1bandits
327
-
328
- // let's check if warm start was hydrated properly!
329
- expectBanditToBeInModelStore (
330
- banditModelStore ,
331
- 'warm_start_bandit' ,
332
- warmStartBanditParameters ,
333
- ) ;
334
- } ) ;
246
+ const warmStartBanditParameters = {
247
+ banditKey : 'warm_start_bandit' ,
248
+ modelName : 'pigeon' ,
249
+ modelVersion : 'warm start' ,
250
+ modelData : {
251
+ gamma : 1.0 ,
252
+ defaultActionScore : 0.0 ,
253
+ actionProbabilityFloor : 0.0 ,
254
+ coefficients : { } ,
255
+ } ,
256
+ } ;
335
257
336
- it ( 'Should not fetch bandits if bandit references model versions shrunk' , async ( ) => {
337
- // Initial fetch
338
- await configurationRequestor . fetchAndStoreConfigurations ( ) ;
258
+ const coldStartBanditParameters = {
259
+ banditKey : 'cold_start_bandit' ,
260
+ modelName : 'falcon' ,
261
+ modelVersion : 'cold start' ,
262
+ modelData : {
263
+ gamma : 1.0 ,
264
+ defaultActionScore : 0.0 ,
265
+ actionProbabilityFloor : 0.0 ,
266
+ coefficients : { } ,
267
+ } ,
268
+ } ;
339
269
340
- // Let's mock UFC response so that cold_start is no longer retrieved
341
- const customResponseMockGenerator = ( url : string ) => {
342
- const responseFile = url . includes ( 'bandits' )
343
- ? MOCK_BANDIT_MODELS_RESPONSE_FILE
344
- : MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
270
+ afterAll ( ( ) => {
271
+ initiateFetchSpy ( defaultResponseMockGenerator ) ;
272
+ } ) ;
345
273
346
- const response = readMockUFCResponse ( responseFile ) ;
274
+ function expectBanditToBeInModelStore (
275
+ store : IConfigurationStore < BanditParameters > ,
276
+ banditKey : string ,
277
+ expectedBanditParameters : BanditParameters ,
278
+ ) {
279
+ const bandit = store . get ( banditKey ) ;
280
+ expect ( bandit ) . toBeTruthy ( ) ;
281
+ expect ( bandit ?. banditKey ) . toBe ( expectedBanditParameters . banditKey ) ;
282
+ expect ( bandit ?. modelVersion ) . toBe ( expectedBanditParameters . modelVersion ) ;
283
+ expect ( bandit ?. modelName ) . toBe ( expectedBanditParameters . modelName ) ;
284
+ expect ( bandit ?. modelData . gamma ) . toBe ( expectedBanditParameters . modelData . gamma ) ;
285
+ expect ( bandit ?. modelData . defaultActionScore ) . toBe (
286
+ expectedBanditParameters . modelData . defaultActionScore ,
287
+ ) ;
288
+ expect ( bandit ?. modelData . actionProbabilityFloor ) . toBe (
289
+ expectedBanditParameters . modelData . actionProbabilityFloor ,
290
+ ) ;
291
+ expect ( bandit ?. modelData . coefficients ) . toStrictEqual (
292
+ expectedBanditParameters . modelData . coefficients ,
293
+ ) ;
294
+ }
347
295
296
+ function injectWarmStartBanditToResponseByUrl (
297
+ url : string ,
298
+ response : IUniversalFlagConfigResponse | IBanditParametersResponse ,
299
+ ) {
348
300
if ( url . includes ( 'config' ) && 'banditReferences' in response ) {
349
- delete response . banditReferences . cold_start_bandit ;
301
+ response . banditReferences . warm_start_bandit = warmStartBanditReference ;
350
302
}
351
- return response ;
352
- } ;
353
-
354
- initiateFetchSpy ( customResponseMockGenerator ) ;
355
- await configurationRequestor . fetchAndStoreConfigurations ( ) ;
356
- expect ( fetchSpy ) . toHaveBeenCalledTimes ( 1 ) ; // only once for UFC
357
-
358
- // cold start should still be in memory
359
- expectBanditToBeInModelStore (
360
- banditModelStore ,
361
- 'cold_start_bandit' ,
362
- coldStartBanditParameters ,
363
- ) ;
364
- } ) ;
365
303
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 ;
304
+ if ( url . includes ( 'bandits' ) && 'bandits' in response ) {
305
+ response . bandits . warm_start_bandit = warmStartBanditParameters ;
395
306
}
396
- return response ;
397
- } ;
398
- injectWarmStart = true ;
399
- initiateFetchSpy ( customResponseMockGenerator ) ;
307
+ }
400
308
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 ) ;
309
+ it ( 'Should fetch bandits if new bandit references model versions appeared' , async ( ) => {
310
+ let updateUFC = false ;
311
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
312
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
313
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 3 ) ;
314
+
315
+ const customResponseMockGenerator = ( url : string ) => {
316
+ const responseFile = url . includes ( 'bandits' )
317
+ ? MOCK_BANDIT_MODELS_RESPONSE_FILE
318
+ : MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
319
+
320
+ const response = readMockUFCResponse ( responseFile ) ;
321
+
322
+ if ( updateUFC === true ) {
323
+ injectWarmStartBanditToResponseByUrl ( url , response ) ;
324
+ }
325
+ return response ;
326
+ } ;
327
+ updateUFC = true ;
328
+ initiateFetchSpy ( customResponseMockGenerator ) ;
329
+
330
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
331
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ; // 2 because fetchSpy was re-initiated, 1UFC and 1bandits
332
+
333
+ // let's check if warm start was hydrated properly!
334
+ expectBanditToBeInModelStore (
335
+ banditModelStore ,
336
+ 'warm_start_bandit' ,
337
+ warmStartBanditParameters ,
338
+ ) ;
339
+ } ) ;
340
+
341
+ it ( 'Should not fetch bandits if bandit references model versions shrunk' , async ( ) => {
342
+ // Initial fetch
343
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
344
+
345
+ // Let's mock UFC response so that cold_start is no longer retrieved
346
+ const customResponseMockGenerator = ( url : string ) => {
347
+ const responseFile = url . includes ( 'bandits' )
348
+ ? MOCK_BANDIT_MODELS_RESPONSE_FILE
349
+ : MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
350
+
351
+ const response = readMockUFCResponse ( responseFile ) ;
352
+
353
+ if ( url . includes ( 'config' ) && 'banditReferences' in response ) {
354
+ delete response . banditReferences . cold_start_bandit ;
355
+ }
356
+ return response ;
357
+ } ;
358
+
359
+ initiateFetchSpy ( customResponseMockGenerator ) ;
360
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
361
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 1 ) ; // only once for UFC
362
+
363
+ // cold start should still be in memory
364
+ expectBanditToBeInModelStore (
365
+ banditModelStore ,
366
+ 'cold_start_bandit' ,
367
+ coldStartBanditParameters ,
368
+ ) ;
369
+ } ) ;
414
370
415
- expectBanditToBeInModelStore (
416
- banditModelStore ,
417
- 'cold_start_bandit' ,
418
- coldStartBanditParameters ,
419
- ) ;
371
+ /**
372
+ * 1. initial call - 1 fetch for ufc 1 for bandits
373
+ * 2. 2nd call - 1 fetch for ufc only; bandits unchanged
374
+ * 3. 3rd call - new bandit ref injected to UFC; 2 fetches, because new bandit appeared
375
+ * 4. 4th call - we remove a bandit from ufc; 1 fetch because there is no need to update.
376
+ * The bandit removed from UFC should still be in memory.
377
+ **/
378
+ it ( 'should fetch bandits based on banditReference change in UFC' , async ( ) => {
379
+ let injectWarmStart = false ;
380
+ let removeColdStartBandit = false ;
381
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
382
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ;
383
+
384
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
385
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 3 ) ;
386
+
387
+ const customResponseMockGenerator = ( url : string ) => {
388
+ const responseFile = url . includes ( 'bandits' )
389
+ ? MOCK_BANDIT_MODELS_RESPONSE_FILE
390
+ : MOCK_FLAGS_WITH_BANDITS_RESPONSE_FILE ;
391
+ const response = readMockUFCResponse ( responseFile ) ;
392
+ if ( injectWarmStart === true ) {
393
+ injectWarmStartBanditToResponseByUrl ( url , response ) ;
394
+ } else if (
395
+ removeColdStartBandit === true &&
396
+ 'banditReferences' in response &&
397
+ url . includes ( 'config' )
398
+ ) {
399
+ delete response . banditReferences . cold_start_bandit ;
400
+ }
401
+ return response ;
402
+ } ;
403
+ injectWarmStart = true ;
404
+ initiateFetchSpy ( customResponseMockGenerator ) ;
405
+
406
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
407
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ;
408
+ expectBanditToBeInModelStore (
409
+ banditModelStore ,
410
+ 'warm_start_bandit' ,
411
+ warmStartBanditParameters ,
412
+ ) ;
413
+
414
+ injectWarmStart = false ;
415
+ removeColdStartBandit = true ;
416
+ initiateFetchSpy ( customResponseMockGenerator ) ;
417
+ await configurationRequestor . fetchAndStoreConfigurations ( ) ;
418
+ expect ( fetchSpy ) . toHaveBeenCalledTimes ( 1 ) ;
419
+
420
+ expectBanditToBeInModelStore (
421
+ banditModelStore ,
422
+ 'cold_start_bandit' ,
423
+ coldStartBanditParameters ,
424
+ ) ;
425
+ } ) ;
420
426
} ) ;
421
427
} ) ;
422
428
} ) ;
0 commit comments