13
13
* See the License for the specific language governing permissions and
14
14
* limitations under the License.
15
15
*/
16
- import { describe , it , expect , beforeEach , afterEach , vi , assert , Mock } from 'vitest' ;
16
+ import { describe , it , expect , beforeEach , afterEach , vi , assert , Mock , beforeAll , afterAll } from 'vitest' ;
17
17
import { sprintf } from '../utils/fns' ;
18
18
import { keyBy } from '../utils/fns' ;
19
- import projectConfig , { ProjectConfig , Region } from './project_config' ;
19
+ import projectConfig , { ProjectConfig , getHoldoutsForFlag } from './project_config' ;
20
20
import { FEATURE_VARIABLE_TYPES , LOG_LEVEL } from '../utils/enums' ;
21
21
import testDatafile from '../tests/test_data' ;
22
22
import configValidator from '../utils/config_validator' ;
@@ -32,11 +32,20 @@ import {
32
32
import { getMockLogger } from '../tests/mock/mock_logger' ;
33
33
import { VariableType } from '../shared_types' ;
34
34
import { OptimizelyError } from '../error/optimizly_error' ;
35
+ import { mock } from 'node:test' ;
35
36
36
37
const buildLogMessageFromArgs = ( args : any [ ] ) => sprintf ( args [ 1 ] , ...args . splice ( 2 ) ) ;
37
38
const cloneDeep = ( obj : any ) => JSON . parse ( JSON . stringify ( obj ) ) ;
38
39
const logger = getMockLogger ( ) ;
39
40
41
+ const mockHoldoutToggle = vi . hoisted ( ( ) => vi . fn ( ) ) ;
42
+
43
+ vi . mock ( '../feature_toggle' , ( ) => {
44
+ return {
45
+ holdout : mockHoldoutToggle ,
46
+ } ;
47
+ } ) ;
48
+
40
49
describe ( 'createProjectConfig' , ( ) => {
41
50
let configObj : ProjectConfig ;
42
51
@@ -298,6 +307,179 @@ describe('createProjectConfig - cmab experiments', () => {
298
307
} ) ;
299
308
} ) ;
300
309
310
+ const getHoldoutDatafile = ( ) => {
311
+ const datafile = testDatafile . getTestDecideProjectConfig ( ) ;
312
+
313
+ // Add holdouts to the datafile
314
+ datafile . holdouts = [
315
+ {
316
+ id : 'holdout_id_1' ,
317
+ key : 'holdout_1' ,
318
+ status : 'Running' ,
319
+ includeFlags : [ ] ,
320
+ excludeFlags : [ ] ,
321
+ audienceIds : [ '13389130056' ] ,
322
+ audienceConditions : [ 'or' , '13389130056' ] ,
323
+ variations : [
324
+ {
325
+ id : 'var_id_1' ,
326
+ key : 'holdout_variation_1' ,
327
+ variables : [ ]
328
+ }
329
+ ] ,
330
+ trafficAllocation : [
331
+ {
332
+ entityId : 'var_id_1' ,
333
+ endOfRange : 5000
334
+ }
335
+ ]
336
+ } ,
337
+ {
338
+ id : 'holdout_id_2' ,
339
+ key : 'holdout_2' ,
340
+ status : 'Running' ,
341
+ includeFlags : [ ] ,
342
+ excludeFlags : [ 'feature_3' ] ,
343
+ audienceIds : [ ] ,
344
+ audienceConditions : [ ] ,
345
+ variations : [
346
+ {
347
+ id : 'var_id_2' ,
348
+ key : 'holdout_variation_2' ,
349
+ variables : [ ]
350
+ }
351
+ ] ,
352
+ trafficAllocation : [
353
+ {
354
+ entityId : 'var_id_2' ,
355
+ endOfRange : 1000
356
+ }
357
+ ]
358
+ } ,
359
+ {
360
+ id : 'holdout_id_3' ,
361
+ key : 'holdout_3' ,
362
+ status : 'Draft' ,
363
+ includeFlags : [ 'feature_1' ] ,
364
+ excludeFlags : [ ] ,
365
+ audienceIds : [ ] ,
366
+ audienceConditions : [ ] ,
367
+ variations : [
368
+ {
369
+ id : 'var_id_2' ,
370
+ key : 'holdout_variation_2' ,
371
+ variables : [ ]
372
+ }
373
+ ] ,
374
+ trafficAllocation : [
375
+ {
376
+ entityId : 'var_id_2' ,
377
+ endOfRange : 1000
378
+ }
379
+ ]
380
+ }
381
+ ] ;
382
+
383
+ return datafile ;
384
+ }
385
+
386
+ describe ( 'createProjectConfig - holdouts, feature toggle is on' , ( ) => {
387
+ beforeAll ( ( ) => {
388
+ mockHoldoutToggle . mockReturnValue ( true ) ;
389
+ } ) ;
390
+
391
+ afterAll ( ( ) => {
392
+ mockHoldoutToggle . mockReset ( ) ;
393
+ } ) ;
394
+
395
+ it ( 'should populate holdouts fields correctly' , function ( ) {
396
+ const datafile = getHoldoutDatafile ( ) ;
397
+
398
+ mockHoldoutToggle . mockReturnValue ( true ) ;
399
+
400
+ const configObj = projectConfig . createProjectConfig ( JSON . parse ( JSON . stringify ( datafile ) ) ) ;
401
+
402
+ expect ( configObj . holdouts ) . toHaveLength ( 3 ) ;
403
+ configObj . holdouts . forEach ( ( holdout , i ) => {
404
+ expect ( holdout ) . toEqual ( expect . objectContaining ( datafile . holdouts [ i ] ) ) ;
405
+ expect ( holdout . variationKeyMap ) . toEqual (
406
+ keyBy ( datafile . holdouts [ i ] . variations , 'key' )
407
+ ) ;
408
+ } ) ;
409
+
410
+ expect ( configObj . holdoutIdMap ) . toEqual ( {
411
+ holdout_id_1 : configObj . holdouts [ 0 ] ,
412
+ holdout_id_2 : configObj . holdouts [ 1 ] ,
413
+ holdout_id_3 : configObj . holdouts [ 2 ] ,
414
+ } ) ;
415
+
416
+ expect ( configObj . globalHoldouts ) . toHaveLength ( 2 ) ;
417
+ expect ( configObj . globalHoldouts ) . toEqual ( [
418
+ configObj . holdouts [ 0 ] , // holdout_1 has empty includeFlags
419
+ configObj . holdouts [ 1 ] // holdout_2 has empty includeFlags
420
+ ] ) ;
421
+
422
+ expect ( configObj . includedHoldouts ) . toEqual ( {
423
+ feature_1 : [ configObj . holdouts [ 2 ] ] , // holdout_3 includes feature_1
424
+ } ) ;
425
+
426
+ expect ( configObj . excludedHoldouts ) . toEqual ( {
427
+ feature_3 : [ configObj . holdouts [ 1 ] ] // holdout_2 excludes feature_3
428
+ } ) ;
429
+
430
+ expect ( configObj . flagHoldoutsMap ) . toEqual ( { } ) ;
431
+ } ) ;
432
+
433
+ it ( 'should handle empty holdouts array' , function ( ) {
434
+ const datafile = testDatafile . getTestProjectConfig ( ) ;
435
+
436
+ const configObj = projectConfig . createProjectConfig ( datafile ) ;
437
+
438
+ expect ( configObj . holdouts ) . toEqual ( [ ] ) ;
439
+ expect ( configObj . holdoutIdMap ) . toEqual ( { } ) ;
440
+ expect ( configObj . globalHoldouts ) . toEqual ( [ ] ) ;
441
+ expect ( configObj . includedHoldouts ) . toEqual ( { } ) ;
442
+ expect ( configObj . excludedHoldouts ) . toEqual ( { } ) ;
443
+ expect ( configObj . flagHoldoutsMap ) . toEqual ( { } ) ;
444
+ } ) ;
445
+ } ) ;
446
+
447
+ describe ( 'getHoldoutsForFlag: feature toggle is on' , ( ) => {
448
+ beforeAll ( ( ) => {
449
+ mockHoldoutToggle . mockReturnValue ( true ) ;
450
+ } ) ;
451
+
452
+ afterAll ( ( ) => {
453
+ mockHoldoutToggle . mockReset ( ) ;
454
+ } ) ;
455
+
456
+ it ( 'should return all applicable holdouts for a flag' , ( ) => {
457
+ const datafile = getHoldoutDatafile ( ) ;
458
+ const configObj = projectConfig . createProjectConfig ( JSON . parse ( JSON . stringify ( datafile ) ) ) ;
459
+
460
+ const feature1Holdouts = getHoldoutsForFlag ( configObj , 'feature_1' ) ;
461
+ expect ( feature1Holdouts ) . toHaveLength ( 3 ) ;
462
+ expect ( feature1Holdouts ) . toEqual ( [
463
+ configObj . holdouts [ 0 ] ,
464
+ configObj . holdouts [ 1 ] ,
465
+ configObj . holdouts [ 2 ] ,
466
+ ] ) ;
467
+
468
+ const feature2Holdouts = getHoldoutsForFlag ( configObj , 'feature_2' ) ;
469
+ expect ( feature2Holdouts ) . toHaveLength ( 2 ) ;
470
+ expect ( feature2Holdouts ) . toEqual ( [
471
+ configObj . holdouts [ 0 ] ,
472
+ configObj . holdouts [ 1 ] ,
473
+ ] ) ;
474
+
475
+ const feature3Holdouts = getHoldoutsForFlag ( configObj , 'feature_3' ) ;
476
+ expect ( feature3Holdouts ) . toHaveLength ( 1 ) ;
477
+ expect ( feature3Holdouts ) . toEqual ( [
478
+ configObj . holdouts [ 0 ] ,
479
+ ] ) ;
480
+ } ) ;
481
+ } ) ;
482
+
301
483
describe ( 'getExperimentId' , ( ) => {
302
484
let testData : Record < string , any > ;
303
485
let configObj : ProjectConfig ;
0 commit comments