@@ -230,6 +230,77 @@ describe('ConfigurationRequestor', () => {
230230 expect ( fetchSpy ) . toHaveBeenCalledTimes ( 3 ) ; // Once just for UFC, bandits should be skipped
231231 } ) ;
232232
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+
233304 it ( 'Should fetch bandits if new bandit references model versions appeared' , async ( ) => {
234305 let updateUFC = false ;
235306 await configurationRequestor . fetchAndStoreConfigurations ( ) ;
@@ -244,34 +315,7 @@ describe('ConfigurationRequestor', () => {
244315 const response = readMockUFCResponse ( responseFile ) ;
245316
246317 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 ) ;
275319 }
276320 return response ;
277321 } ;
@@ -282,15 +326,11 @@ describe('ConfigurationRequestor', () => {
282326 expect ( fetchSpy ) . toHaveBeenCalledTimes ( 2 ) ; // 2 because fetchSpy was re-initiated, 1UFC and 1bandits
283327
284328 // 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+ ) ;
294334 } ) ;
295335
296336 it ( 'Should not fetch bandits if bandit references model versions shrunk' , async ( ) => {
@@ -316,15 +356,67 @@ describe('ConfigurationRequestor', () => {
316356 expect ( fetchSpy ) . toHaveBeenCalledTimes ( 1 ) ; // only once for UFC
317357
318358 // 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+ ) ;
328420 } ) ;
329421 } ) ;
330422 } ) ;
0 commit comments