@@ -1760,8 +1760,7 @@ public void evaluateLocalHoldouts_returnsHoldoutDecisionWhenUserBucketed() {
17601760
17611761 DecisionResponse <FeatureDecision > response = decisionService .evaluateLocalHoldouts (
17621762 targetedRule , localHoldoutConfig ,
1763- optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
1764- false
1763+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ())
17651764 );
17661765
17671766 assertNotNull (response .getResult ());
@@ -1781,8 +1780,7 @@ public void evaluateLocalHoldouts_returnsNullWhenNoHoldoutsForRule() {
17811780
17821781 DecisionResponse <FeatureDecision > response = decisionService .evaluateLocalHoldouts (
17831782 untargetedRule , localHoldoutConfig ,
1784- optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
1785- false
1783+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ())
17861784 );
17871785
17881786 assertNull (response .getResult ());
@@ -1798,8 +1796,7 @@ public void evaluateLocalHoldouts_returnsNullWhenConfigHasNoHoldouts() {
17981796
17991797 DecisionResponse <FeatureDecision > response = decisionService .evaluateLocalHoldouts (
18001798 rule , noHoldoutConfig ,
1801- optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
1802- false
1799+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ())
18031800 );
18041801
18051802 assertNull (response .getResult ());
@@ -1974,7 +1971,7 @@ public void excludeTargetedDeliveries_globalHoldoutFalse_blocksAllRules() {
19741971 }
19751972
19761973 @ Test
1977- public void excludeTargetedDeliveries_globalHoldoutTrue_blocksExperimentRules () {
1974+ public void excludeTargetedDeliveries_globalHoldoutTrue_skipsExperimentRules () {
19781975 ProjectConfig config = ValidProjectConfigV4 .generateValidProjectConfigV4_globalHoldoutExcludeTargetedDeliveries ();
19791976
19801977 Bucketer bucketer = new Bucketer ();
@@ -1986,8 +1983,12 @@ public void excludeTargetedDeliveries_globalHoldoutTrue_blocksExperimentRules()
19861983 config
19871984 ).getResult ();
19881985
1989- assertNotNull (decision );
1990- assertEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .decisionSource );
1986+ assertTrue ("With excludeTargetedDeliveries=true and no rollout, variation should be null" ,
1987+ decision == null || decision .variation == null );
1988+ if (decision != null ) {
1989+ assertNotNull ("holdoutDecision should be attached" , decision .holdoutDecision );
1990+ assertEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .holdoutDecision .decisionSource );
1991+ }
19911992 }
19921993
19931994 @ Test
@@ -2009,7 +2010,7 @@ public void excludeTargetedDeliveries_globalHoldoutTrue_allowsDeliveryRules() {
20092010 }
20102011
20112012 @ Test
2012- public void excludeTargetedDeliveries_globalHoldoutTrue_noDeliveryMatch_returnsHoldout () {
2013+ public void excludeTargetedDeliveries_globalHoldoutTrue_noDeliveryMatch_returnsNullWithHoldoutAttached () {
20132014 ProjectConfig config = ValidProjectConfigV4 .generateValidProjectConfigV4_globalHoldoutExcludeTargetedDeliveries ();
20142015
20152016 Bucketer bucketer = new Bucketer ();
@@ -2021,28 +2022,30 @@ public void excludeTargetedDeliveries_globalHoldoutTrue_noDeliveryMatch_returnsH
20212022 config
20222023 ).getResult ();
20232024
2024- assertNotNull (decision );
2025- assertEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .decisionSource );
2026- assertEquals (HOLDOUT_GLOBAL_EXCLUDE_TARGETED_DELIVERIES , decision .experiment );
2025+ assertTrue ("When excludeTargetedDeliveries=true and no delivery match, variation should be null" ,
2026+ decision == null || decision .variation == null );
2027+ if (decision != null ) {
2028+ assertNotNull ("holdoutDecision should be attached" , decision .holdoutDecision );
2029+ assertEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .holdoutDecision .decisionSource );
2030+ }
20272031 }
20282032
20292033 @ Test
2030- public void excludeTargetedDeliveries_localHoldoutTrue_skipsHoldoutForDeliveryRules () {
2034+ public void excludeTargetedDeliveries_localHoldoutTrue_appliesHoldoutForDeliveryRules () {
20312035 ProjectConfig config = ValidProjectConfigV4 .generateValidProjectConfigV4_localHoldoutExcludeTargetedDeliveries ();
20322036
20332037 Bucketer bucketer = new Bucketer ();
20342038 DecisionService ds = new DecisionService (bucketer , mockErrorHandler , null , mockCmabService );
20352039
2036- Experiment deliveryRule = mock (Experiment .class );
2037- when (deliveryRule .getId ()).thenReturn (ValidProjectConfigV4 .EXPERIMENT_BASIC_EXPERIMENT_KEY );
2040+ Experiment deliveryRule = config .getExperimentIdMapping ().get ("1323241596" );
20382041
20392042 DecisionResponse <FeatureDecision > response = ds .evaluateLocalHoldouts (
20402043 deliveryRule , config ,
2041- optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
2042- true
2044+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ())
20432045 );
20442046
2045- assertNull (response .getResult ());
2047+ assertNotNull (response .getResult ());
2048+ assertEquals (FeatureDecision .DecisionSource .HOLDOUT , response .getResult ().decisionSource );
20462049 }
20472050
20482051 @ Test
@@ -2073,8 +2076,7 @@ public void excludeTargetedDeliveries_evaluateLocalHoldouts_falseDoesNotSkipForD
20732076
20742077 DecisionResponse <FeatureDecision > response = ds .evaluateLocalHoldouts (
20752078 targetedRule , config ,
2076- optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
2077- true
2079+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ())
20782080 );
20792081
20802082 assertNotNull (response .getResult ());
@@ -2105,6 +2107,67 @@ public void excludeTargetedDeliveries_forcedDecisionBeatsLocalHoldoutWithExclude
21052107 assertNotEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .decisionSource );
21062108 }
21072109
2110+ //========= local holdout ignores excludeTargetedDeliveries tests =========/
2111+
2112+ @ Test
2113+ public void localHoldout_ignoresExcludeTargetedDeliveries () {
2114+ ProjectConfig config = ValidProjectConfigV4 .generateValidProjectConfigV4_localHoldoutExcludeTargetedDeliveries ();
2115+
2116+ Bucketer bucketer = new Bucketer ();
2117+ DecisionService ds = new DecisionService (bucketer , mockErrorHandler , null , mockCmabService );
2118+
2119+ FeatureDecision decision = ds .getVariationForFeature (
2120+ ValidProjectConfigV4 .FEATURE_FLAG_BASIC_EXPERIMENT_FEATURE ,
2121+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
2122+ config
2123+ ).getResult ();
2124+
2125+ assertNotNull (decision );
2126+ assertEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .decisionSource );
2127+ assertEquals (HOLDOUT_LOCAL_EXCLUDE_TARGETED_DELIVERIES , decision .experiment );
2128+ }
2129+
2130+ @ Test
2131+ public void globalHoldout_excludeTD_rolloutReturnsNull_returnsNull () {
2132+ ProjectConfig config = ValidProjectConfigV4 .generateValidProjectConfigV4_globalHoldoutExcludeTargetedDeliveries ();
2133+
2134+ Bucketer bucketer = new Bucketer ();
2135+ DecisionService ds = new DecisionService (bucketer , mockErrorHandler , null , mockCmabService );
2136+
2137+ FeatureDecision decision = ds .getVariationForFeature (
2138+ FEATURE_FLAG_BOOLEAN_FEATURE ,
2139+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
2140+ config
2141+ ).getResult ();
2142+
2143+ assertTrue ("When excludeTargetedDeliveries=true and rollout returns null, result should be null or have null variation" ,
2144+ decision == null || decision .variation == null );
2145+ if (decision != null ) {
2146+ assertNotEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .decisionSource );
2147+ }
2148+ }
2149+
2150+ @ Test
2151+ public void globalHoldout_excludeTD_holdoutDecisionAttached () {
2152+ ProjectConfig config = ValidProjectConfigV4 .generateValidProjectConfigV4_globalHoldoutExcludeTargetedDeliveries ();
2153+
2154+ Bucketer bucketer = new Bucketer ();
2155+ DecisionService ds = new DecisionService (bucketer , mockErrorHandler , null , mockCmabService );
2156+
2157+ FeatureDecision decision = ds .getVariationForFeature (
2158+ FEATURE_FLAG_SINGLE_VARIABLE_INTEGER ,
2159+ optimizely .createUserContext ("any_user" , Collections .<String , Object >emptyMap ()),
2160+ config
2161+ ).getResult ();
2162+
2163+ assertNotNull (decision );
2164+ assertEquals (FeatureDecision .DecisionSource .ROLLOUT , decision .decisionSource );
2165+ assertNotNull ("holdoutDecision should be attached when user is in holdout with excludeTargetedDeliveries=true" ,
2166+ decision .holdoutDecision );
2167+ assertEquals (FeatureDecision .DecisionSource .HOLDOUT , decision .holdoutDecision .decisionSource );
2168+ assertEquals (HOLDOUT_GLOBAL_EXCLUDE_TARGETED_DELIVERIES , decision .holdoutDecision .experiment );
2169+ }
2170+
21082171 private Experiment createMockCmabExperiment () {
21092172 List <Variation > variations = Arrays .asList (
21102173 new Variation ("111151" , "variation_1" ),
0 commit comments