@@ -2204,4 +2204,158 @@ public void decide_for_keys_with_holdout() throws Exception {
22042204 logbackVerifier .expectMessage (Level .INFO , expectedReason );
22052205 }
22062206
2207+ private Optimizely createOptimizelyWithLocalHoldouts (boolean sendFlagDecisions ) throws Exception {
2208+ String datafile = Resources .toString (Resources .getResource ("config/local-holdouts-project-config.json" ), Charsets .UTF_8 );
2209+ if (!sendFlagDecisions ) {
2210+ datafile = datafile .replace ("\" sendFlagDecisions\" : true" , "\" sendFlagDecisions\" : false" );
2211+ }
2212+ return new Optimizely .Builder ().withDatafile (datafile ).withEventProcessor (new ForwardingEventProcessor (eventHandler , null )).build ();
2213+ }
2214+
2215+ private void expectLocalHoldoutImpression (String holdoutId , String holdoutKey , String flagKey , Map <String , ?> attributes ) {
2216+ DecisionMetadata metadata = new DecisionMetadata .Builder ()
2217+ .setFlagKey (flagKey )
2218+ .setRuleKey (holdoutKey )
2219+ .setRuleType ("holdout" )
2220+ .setVariationKey ("ho_off_key" )
2221+ .setEnabled (false )
2222+ .build ();
2223+ eventHandler .expectImpression (holdoutId , null , userId , attributes , metadata );
2224+ }
2225+
2226+ private void expectRuleImpression (String experimentId , String variationId , String flagKey , String ruleKey , String ruleType , String variationKey , Map <String , ?> attributes ) {
2227+ DecisionMetadata metadata = new DecisionMetadata .Builder ()
2228+ .setFlagKey (flagKey )
2229+ .setRuleKey (ruleKey )
2230+ .setRuleType (ruleType )
2231+ .setVariationKey (variationKey )
2232+ .setEnabled (true )
2233+ .build ();
2234+ eventHandler .expectImpression (experimentId , variationId , userId , attributes , metadata );
2235+ }
2236+
2237+ @ Test
2238+ public void decide_localHoldoutOnFirstExperimentRule_servedBySecondExperimentRule () throws Exception {
2239+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2240+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "exp1" );
2241+
2242+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_flag" );
2243+
2244+ assertEquals ("exp_2_on" , decision .getVariationKey ());
2245+ assertEquals ("exp_rule_2" , decision .getRuleKey ());
2246+ assertTrue (decision .getEnabled ());
2247+ expectRuleImpression ("5200000002" , "5200000012" , "lh_flag" , "exp_rule_2" , "feature-test" , "exp_2_on" , attrs );
2248+ expectLocalHoldoutImpression ("5300000001" , "lh_exp_rule_1" , "lh_flag" , attrs );
2249+ }
2250+
2251+ @ Test
2252+ public void decide_localHoldoutsOnSeveralRules_sendEventPerHit () throws Exception {
2253+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2254+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "exp1,exp2" );
2255+
2256+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_flag" );
2257+
2258+ assertEquals ("delivery_on" , decision .getVariationKey ());
2259+ assertEquals ("delivery_rule" , decision .getRuleKey ());
2260+ expectRuleImpression ("5400000001" , "5400000011" , "lh_flag" , "delivery_rule" , "rollout" , "delivery_on" , attrs );
2261+ expectLocalHoldoutImpression ("5300000001" , "lh_exp_rule_1" , "lh_flag" , attrs );
2262+ expectLocalHoldoutImpression ("5300000002" , "lh_exp_rule_2" , "lh_flag" , attrs );
2263+ }
2264+
2265+ @ Test
2266+ public void decide_localHoldoutHitAndNoRuleServes_stillSendsHoldoutEvents () throws Exception {
2267+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2268+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "delivery,everyone" );
2269+
2270+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_delivery_flag" );
2271+
2272+ assertNull (decision .getVariationKey ());
2273+ assertNull (decision .getRuleKey ());
2274+ assertFalse (decision .getEnabled ());
2275+ DecisionMetadata offMetadata = new DecisionMetadata .Builder ()
2276+ .setFlagKey ("lh_delivery_flag" )
2277+ .setRuleKey ("" )
2278+ .setRuleType ("rollout" )
2279+ .setVariationKey ("" )
2280+ .setEnabled (false )
2281+ .build ();
2282+ eventHandler .expectImpression (null , null , userId , attrs , offMetadata );
2283+ expectLocalHoldoutImpression ("5300000003" , "lh_delivery_rule" , "lh_delivery_flag" , attrs );
2284+ expectLocalHoldoutImpression ("5300000004" , "lh_everyone_else_rule" , "lh_delivery_flag" , attrs );
2285+ }
2286+
2287+ @ Test
2288+ public void decide_localHoldoutOnDeliveryRule_sendsHoldoutEventWhenSendFlagDecisionsOff () throws Exception {
2289+ Optimizely opt = createOptimizelyWithLocalHoldouts (false );
2290+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "delivery" );
2291+
2292+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_delivery_flag" );
2293+
2294+ assertEquals ("everyone_else_on" , decision .getVariationKey ());
2295+ assertEquals ("everyone_else_rule" , decision .getRuleKey ());
2296+ assertTrue (decision .getEnabled ());
2297+ expectLocalHoldoutImpression ("5300000003" , "lh_delivery_rule" , "lh_delivery_flag" , attrs );
2298+ }
2299+
2300+ @ Test
2301+ public void decide_localHoldoutOnFirstDeliveryRule_servedBySecondDeliveryRule () throws Exception {
2302+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2303+ Map <String , Object > attrs = Collections .emptyMap ();
2304+
2305+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_multi_delivery_flag" );
2306+
2307+ assertEquals ("second_delivery_on" , decision .getVariationKey ());
2308+ assertEquals ("second_delivery_rule" , decision .getRuleKey ());
2309+ assertTrue (decision .getEnabled ());
2310+ expectRuleImpression ("5400000004" , "5400000014" , "lh_multi_delivery_flag" , "second_delivery_rule" , "rollout" , "second_delivery_on" , attrs );
2311+ expectLocalHoldoutImpression ("5300000006" , "lh_first_delivery_rule" , "lh_multi_delivery_flag" , attrs );
2312+ }
2313+
2314+ @ Test
2315+ public void decide_localHoldoutWithDisableDecisionEvent_sendsNoEvents () throws Exception {
2316+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2317+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "exp1,delivery" );
2318+
2319+ OptimizelyDecision decision = opt .createUserContext (userId , attrs )
2320+ .decide ("lh_flag" , Collections .singletonList (OptimizelyDecideOption .DISABLE_DECISION_EVENT ));
2321+
2322+ assertEquals ("exp_2_on" , decision .getVariationKey ());
2323+ eventHandler .expectCalls (0 );
2324+ }
2325+
2326+ @ Test
2327+ public void decide_oneLocalHoldoutOnTwoRules_sendsEventPerRule () throws Exception {
2328+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2329+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "multi" );
2330+
2331+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_flag" );
2332+
2333+ assertEquals ("delivery_on" , decision .getVariationKey ());
2334+ expectRuleImpression ("5400000001" , "5400000011" , "lh_flag" , "delivery_rule" , "rollout" , "delivery_on" , attrs );
2335+ expectLocalHoldoutImpression ("5300000005" , "lh_both_exp_rules" , "lh_flag" , attrs );
2336+ expectLocalHoldoutImpression ("5300000005" , "lh_both_exp_rules" , "lh_flag" , attrs );
2337+ }
2338+
2339+ @ Test
2340+ public void decide_noLocalHoldoutHit_servedByFirstRule () throws Exception {
2341+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2342+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "none" );
2343+
2344+ OptimizelyDecision decision = opt .createUserContext (userId , attrs ).decide ("lh_flag" );
2345+
2346+ assertEquals ("exp_1_on" , decision .getVariationKey ());
2347+ assertEquals ("exp_rule_1" , decision .getRuleKey ());
2348+ expectRuleImpression ("5200000001" , "5200000011" , "lh_flag" , "exp_rule_1" , "feature-test" , "exp_1_on" , attrs );
2349+ }
2350+
2351+ @ Test
2352+ public void isFeatureEnabled_skipsLocallyHeldOutRuleWithoutHoldoutEvent () throws Exception {
2353+ Optimizely opt = createOptimizelyWithLocalHoldouts (true );
2354+ Map <String , Object > attrs = Collections .singletonMap ("lh" , "exp1" );
2355+
2356+ assertTrue (opt .isFeatureEnabled ("lh_flag" , userId , attrs ));
2357+
2358+ expectRuleImpression ("5200000002" , "5200000012" , "lh_flag" , "exp_rule_2" , "feature-test" , "exp_2_on" , attrs );
2359+ }
2360+
22072361}
0 commit comments