@@ -29,6 +29,7 @@ import { DECISION_SOURCES } from '../utils/enums';
29
29
import OptimizelyUserContext from '../optimizely_user_context' ;
30
30
import { newErrorDecision } from '../optimizely_decision' ;
31
31
import { ImpressionEvent } from '../event_processor/event_builder/user_event' ;
32
+ import { OptimizelyDecideOption } from '../shared_types' ;
32
33
33
34
describe ( 'Optimizely' , ( ) => {
34
35
const eventDispatcher = {
@@ -212,5 +213,252 @@ describe('Optimizely', () => {
212
213
const event = processSpy . mock . calls [ 0 ] [ 0 ] as ImpressionEvent ;
213
214
expect ( event . cmabUuid ) . toBe ( 'uuid-cmab' ) ;
214
215
} ) ;
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
+ } ) ;
215
386
} ) ;
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
+ } )
216
464
} ) ;
0 commit comments