@@ -485,6 +485,87 @@ describe('createProjectConfig - local holdouts (FSSDK-12369, FSSDK-12760)', () =
485485 expect ( forRuleB [ 0 ] . id ) . toBe ( 'local_holdout_rule_a_id' ) ;
486486 } ) ;
487487
488+ it ( 'getHoldoutsForRule should only return holdouts targeting that rule and exclude others' , ( ) => {
489+ const datafile = cloneDeep ( makeHoldoutsDatafile ( ) ) ;
490+ ( datafile as any ) . localHoldouts = [
491+ {
492+ id : 'holdout_for_rule_x' ,
493+ key : 'holdout_rule_x' ,
494+ status : 'Running' ,
495+ includedFlags : [ ] ,
496+ excludedFlags : [ ] ,
497+ audienceIds : [ ] ,
498+ audienceConditions : [ ] ,
499+ includedRules : [ 'rule_x' ] ,
500+ variations : [ { id : 'var_x' , key : 'var_x' , variables : [ ] } ] ,
501+ trafficAllocation : [ { entityId : 'var_x' , endOfRange : 5000 } ] ,
502+ } ,
503+ {
504+ id : 'holdout_for_rule_y' ,
505+ key : 'holdout_rule_y' ,
506+ status : 'Running' ,
507+ includedFlags : [ ] ,
508+ excludedFlags : [ ] ,
509+ audienceIds : [ ] ,
510+ audienceConditions : [ ] ,
511+ includedRules : [ 'rule_y' ] ,
512+ variations : [ { id : 'var_y' , key : 'var_y' , variables : [ ] } ] ,
513+ trafficAllocation : [ { entityId : 'var_y' , endOfRange : 5000 } ] ,
514+ } ,
515+ {
516+ id : 'holdout_for_both' ,
517+ key : 'holdout_both' ,
518+ status : 'Running' ,
519+ includedFlags : [ ] ,
520+ excludedFlags : [ ] ,
521+ audienceIds : [ ] ,
522+ audienceConditions : [ ] ,
523+ includedRules : [ 'rule_x' , 'rule_y' ] ,
524+ variations : [ { id : 'var_both' , key : 'var_both' , variables : [ ] } ] ,
525+ trafficAllocation : [ { entityId : 'var_both' , endOfRange : 5000 } ] ,
526+ } ,
527+ ] ;
528+ const config = projectConfig . createProjectConfig ( datafile as any ) ;
529+
530+ const forRuleX = getHoldoutsForRule ( config , 'rule_x' ) ;
531+ const forRuleXIds = forRuleX . map ( h => h . id ) . sort ( ) ;
532+ expect ( forRuleXIds ) . toEqual ( [ 'holdout_for_both' , 'holdout_for_rule_x' ] ) ;
533+
534+ const forRuleY = getHoldoutsForRule ( config , 'rule_y' ) ;
535+ const forRuleYIds = forRuleY . map ( h => h . id ) . sort ( ) ;
536+ expect ( forRuleYIds ) . toEqual ( [ 'holdout_for_both' , 'holdout_for_rule_y' ] ) ;
537+
538+ // rule_x results must not contain holdout_for_rule_y and vice versa
539+ expect ( forRuleXIds ) . not . toContain ( 'holdout_for_rule_y' ) ;
540+ expect ( forRuleYIds ) . not . toContain ( 'holdout_for_rule_x' ) ;
541+ } ) ;
542+
543+ it ( 'getHoldoutsForRule should not return global holdouts' , ( ) => {
544+ const datafile = cloneDeep ( makeHoldoutsDatafile ( ) ) ;
545+ ( datafile as any ) . localHoldouts = [
546+ {
547+ id : 'local_only' ,
548+ key : 'local_only' ,
549+ status : 'Running' ,
550+ includedFlags : [ ] ,
551+ excludedFlags : [ ] ,
552+ audienceIds : [ ] ,
553+ audienceConditions : [ ] ,
554+ includedRules : [ 'rule_z' ] ,
555+ variations : [ { id : 'var_local' , key : 'var_local' , variables : [ ] } ] ,
556+ trafficAllocation : [ { entityId : 'var_local' , endOfRange : 5000 } ] ,
557+ } ,
558+ ] ;
559+ const config = projectConfig . createProjectConfig ( datafile as any ) ;
560+
561+ const forRuleZ = getHoldoutsForRule ( config , 'rule_z' ) ;
562+ expect ( forRuleZ ) . toHaveLength ( 1 ) ;
563+ expect ( forRuleZ [ 0 ] . id ) . toBe ( 'local_only' ) ;
564+ // global holdout must never appear in rule lookups
565+ const allRuleHoldoutIds = forRuleZ . map ( h => h . id ) ;
566+ expect ( allRuleHoldoutIds ) . not . toContain ( 'global_holdout_id' ) ;
567+ } ) ;
568+
488569 it ( 'getHoldoutsForRule should return empty array for an unknown rule ID' , ( ) => {
489570 const config = projectConfig . createProjectConfig ( cloneDeep ( makeHoldoutsDatafile ( ) ) as any ) ;
490571 const forUnknown = getHoldoutsForRule ( config , 'nonexistent_rule' ) ;
0 commit comments