Skip to content

Commit dca096d

Browse files
more tests for bandits polling
1 parent 17b7ded commit dca096d

File tree

1 file changed

+138
-46
lines changed

1 file changed

+138
-46
lines changed

src/configuration-requestor.spec.ts

Lines changed: 138 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)