Skip to content

Commit 83b7017

Browse files
[FSSDK-11529] test addition + layer logic adjustment
1 parent 0618802 commit 83b7017

File tree

3 files changed

+252
-2
lines changed

3 files changed

+252
-2
lines changed

lib/event_processor/event_builder/user_event.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828

2929
import { EventTags, UserAttributes } from '../../shared_types';
3030
import { LoggerFacade } from '../../logging/logger';
31+
import { DECISION_SOURCES } from '../../common_exports';
3132

3233
export type VisitorAttribute = {
3334
entityId: string
@@ -185,7 +186,8 @@ export const buildImpressionEvent = function({
185186
const variationId = decision.getVariationId(decisionObj);
186187
const cmabUuid = decisionObj.cmabUuid;
187188

188-
const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null;
189+
const layerId =
190+
experimentId !== null && ruleType !== DECISION_SOURCES.HOLDOUT ? getLayerId(configObj, experimentId) : null;
189191

190192
return {
191193
...buildBaseEvent({

lib/feature_toggle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
* flag and all associated checks can be removed from the codebase.
3232
*/
3333

34-
export const holdout = () => false;
34+
export const holdout = () => true;

lib/optimizely/index.spec.ts

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { DECISION_SOURCES } from '../utils/enums';
2929
import OptimizelyUserContext from '../optimizely_user_context';
3030
import { newErrorDecision } from '../optimizely_decision';
3131
import { ImpressionEvent } from '../event_processor/event_builder/user_event';
32+
import { OptimizelyDecideOption } from '../shared_types';
3233

3334
describe('Optimizely', () => {
3435
const eventDispatcher = {
@@ -212,5 +213,252 @@ describe('Optimizely', () => {
212213
const event = processSpy.mock.calls[0][0] as ImpressionEvent;
213214
expect(event.cmabUuid).toBe('uuid-cmab');
214215
});
216+
217+
it('should dispatch impression event for holdout decision', async () => {
218+
const datafile = getDecisionTestDatafile();
219+
220+
datafile.holdouts = [
221+
{
222+
id: 'holdout_test_id',
223+
key: 'holdout_test_key',
224+
status: 'Running',
225+
includeFlags: [],
226+
excludeFlags: [],
227+
audienceIds: [],
228+
audienceConditions: [],
229+
variations: [
230+
{
231+
id: 'holdout_variation_id',
232+
key: 'holdout_variation_key',
233+
variables: [],
234+
featureEnabled: false
235+
}
236+
],
237+
trafficAllocation: [
238+
{
239+
entityId: 'holdout_variation_id',
240+
endOfRange: 10000
241+
}
242+
]
243+
}
244+
];
245+
246+
const projectConfig = createProjectConfig(datafile);
247+
248+
const projectConfigManager = getMockProjectConfigManager({
249+
initConfig: projectConfig,
250+
});
251+
252+
const mockEventDispatcher = {
253+
dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })),
254+
};
255+
const eventProcessor = getForwardingEventProcessor(mockEventDispatcher);
256+
const processSpy = vi.spyOn(eventProcessor, 'process');
257+
258+
const optimizely = new Optimizely({
259+
clientEngine: 'node-sdk',
260+
projectConfigManager,
261+
eventProcessor,
262+
jsonSchemaValidator,
263+
logger,
264+
odpManager,
265+
disposable: true,
266+
cmabService: {} as any
267+
});
268+
269+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
270+
// @ts-ignore
271+
const decisionService = optimizely.decisionService;
272+
vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => {
273+
return Value.of('async', [{
274+
error: false,
275+
result: {
276+
variation: projectConfig.holdouts[0].variations[0],
277+
experiment: projectConfig.holdouts[0],
278+
decisionSource: DECISION_SOURCES.HOLDOUT,
279+
},
280+
reasons: [],
281+
}]);
282+
});
283+
284+
const user = new OptimizelyUserContext({
285+
optimizely,
286+
userId: 'test_user',
287+
attributes: {},
288+
});
289+
290+
const decision = await optimizely.decideAsync(user, 'flag_1', []);
291+
292+
expect(decision.ruleKey).toBe('holdout_test_key');
293+
expect(decision.flagKey).toBe('flag_1');
294+
expect(decision.variationKey).toBe('holdout_variation_key');
295+
expect(decision.enabled).toBe(false);
296+
297+
expect(eventProcessor.process).toHaveBeenCalledOnce();
298+
299+
const event = processSpy.mock.calls[0][0] as ImpressionEvent;
300+
301+
expect(event.type).toBe('impression');
302+
expect(event.ruleKey).toBe('holdout_test_key');
303+
expect(event.ruleType).toBe('holdout');
304+
expect(event.enabled).toBe(false);
305+
});
306+
307+
it('should not dispatch impression event for holdout when DISABLE_DECISION_EVENT is used', async () => {
308+
const datafile = getDecisionTestDatafile();
309+
310+
datafile.holdouts = [
311+
{
312+
id: 'holdout_test_id',
313+
key: 'holdout_test_key',
314+
status: 'Running',
315+
includeFlags: [],
316+
excludeFlags: [],
317+
audienceIds: [],
318+
audienceConditions: [],
319+
variations: [
320+
{
321+
id: 'holdout_variation_id',
322+
key: 'holdout_variation_key',
323+
variables: [],
324+
featureEnabled: false
325+
}
326+
],
327+
trafficAllocation: [
328+
{
329+
entityId: 'holdout_variation_id',
330+
endOfRange: 10000
331+
}
332+
]
333+
}
334+
];
335+
336+
const projectConfig = createProjectConfig(datafile);
337+
338+
const projectConfigManager = getMockProjectConfigManager({
339+
initConfig: projectConfig,
340+
});
341+
342+
const mockEventDispatcher = {
343+
dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })),
344+
};
345+
const eventProcessor = getForwardingEventProcessor(mockEventDispatcher);
346+
vi.spyOn(eventProcessor, 'process');
347+
348+
const optimizely = new Optimizely({
349+
clientEngine: 'node-sdk',
350+
projectConfigManager,
351+
eventProcessor,
352+
jsonSchemaValidator,
353+
logger,
354+
odpManager,
355+
disposable: true,
356+
cmabService: {} as any
357+
});
358+
359+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
360+
// @ts-ignore
361+
const decisionService = optimizely.decisionService;
362+
vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => {
363+
return Value.of('async', [{
364+
error: false,
365+
result: {
366+
variation: projectConfig.holdouts![0].variations[0],
367+
experiment: projectConfig.holdouts![0],
368+
decisionSource: DECISION_SOURCES.HOLDOUT,
369+
},
370+
reasons: [],
371+
}]);
372+
});
373+
374+
const user = new OptimizelyUserContext({
375+
optimizely,
376+
userId: 'test_user',
377+
attributes: {},
378+
});
379+
380+
const decision = await optimizely.decideAsync(user, 'flag_1', [OptimizelyDecideOption.DISABLE_DECISION_EVENT]);
381+
382+
expect(decision.ruleKey).toBe('holdout_test_key');
383+
expect(decision.enabled).toBe(false);
384+
expect(eventProcessor.process).not.toHaveBeenCalled();
385+
});
215386
});
387+
describe('isFeatureEnabled', () => {
388+
it('should dispatch impression event for holdout decision', async () => {
389+
const datafile = getDecisionTestDatafile();
390+
datafile.holdouts = [
391+
{
392+
id: 'holdout_test_id',
393+
key: 'holdout_test_key',
394+
status: 'Running',
395+
includeFlags: [],
396+
excludeFlags: [],
397+
audienceIds: [],
398+
audienceConditions: [],
399+
variations: [
400+
{
401+
id: 'holdout_variation_id',
402+
key: 'holdout_variation_key',
403+
variables: [],
404+
featureEnabled: false
405+
}
406+
],
407+
trafficAllocation: [
408+
{
409+
entityId: 'holdout_variation_id',
410+
endOfRange: 10000
411+
}
412+
]
413+
}
414+
];
415+
416+
const projectConfig = createProjectConfig(datafile);
417+
const projectConfigManager = getMockProjectConfigManager({
418+
initConfig: projectConfig,
419+
});
420+
const mockEventDispatcher = {
421+
dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })),
422+
};
423+
const eventProcessor = getForwardingEventProcessor(mockEventDispatcher);
424+
vi.spyOn(eventProcessor, 'process');
425+
426+
const optimizely = new Optimizely({
427+
clientEngine: 'node-sdk',
428+
projectConfigManager,
429+
eventProcessor,
430+
jsonSchemaValidator,
431+
logger,
432+
odpManager,
433+
disposable: true,
434+
cmabService: {} as any
435+
});
436+
437+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
438+
// @ts-ignore
439+
const decisionService = optimizely.decisionService;
440+
vi.spyOn(decisionService, 'getVariationForFeature').mockReturnValue({
441+
error: false,
442+
result: {
443+
variation: projectConfig.holdouts![0].variations[0],
444+
experiment: projectConfig.holdouts![0],
445+
decisionSource: DECISION_SOURCES.HOLDOUT,
446+
},
447+
reasons: [],
448+
});
449+
const result = optimizely.isFeatureEnabled('flag_1', 'test_user', {});
450+
451+
expect(result).toBe(false);
452+
453+
expect(eventProcessor.process).toHaveBeenCalledOnce();
454+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
455+
// @ts-ignore
456+
const event = eventProcessor.process.mock.calls[0][0] as ImpressionEvent;
457+
458+
expect(event.type).toBe('impression');
459+
expect(event.ruleKey).toBe('holdout_test_key');
460+
expect(event.ruleType).toBe('holdout');
461+
expect(event.enabled).toBe(false);
462+
});
463+
})
216464
});

0 commit comments

Comments
 (0)