57
57
import static junit .framework .TestCase .assertEquals ;
58
58
import static junit .framework .TestCase .assertTrue ;
59
59
import static org .junit .Assert .*;
60
+ import static org .mockito .Matchers .any ;
61
+ import static org .mockito .Matchers .anyObject ;
62
+ import static org .mockito .Matchers .eq ;
60
63
import static org .mockito .Mockito .*;
61
64
62
65
public class OptimizelyUserContextTest {
@@ -76,6 +79,8 @@ public class OptimizelyUserContextTest {
76
79
Map <String , FeatureFlag > featureKeyMapping ;
77
80
Map <String , Group > groupIdMapping ;
78
81
82
+ private String holdoutDatafile ;
83
+
79
84
@ Before
80
85
public void setUp () throws Exception {
81
86
datafile = Resources .toString (Resources .getResource ("config/decide-project-config.json" ), Charsets .UTF_8 );
@@ -85,6 +90,16 @@ public void setUp() throws Exception {
85
90
.build ();
86
91
}
87
92
93
+ private Optimizely createOptimizelyWithHoldouts () throws Exception {
94
+ if (holdoutDatafile == null ) {
95
+ holdoutDatafile = com .google .common .io .Resources .toString (
96
+ com .google .common .io .Resources .getResource ("config/holdouts-project-config.json" ),
97
+ com .google .common .base .Charsets .UTF_8
98
+ );
99
+ }
100
+ return new Optimizely .Builder ().withDatafile (holdoutDatafile ).withEventProcessor (new ForwardingEventProcessor (eventHandler , null )).build ();
101
+ }
102
+
88
103
@ Test
89
104
public void optimizelyUserContext_withAttributes () {
90
105
Map <String , Object > attributes = Collections .singletonMap (ATTRIBUTE_HOUSE_KEY , AUDIENCE_GRYFFINDOR_VALUE );
@@ -749,7 +764,7 @@ public void decisionNotification() {
749
764
public void decideOptions_bypassUPS () throws Exception {
750
765
String flagKey = "feature_2" ; // embedding experiment: "exp_no_audience"
751
766
String experimentId = "10420810910" ; // "exp_no_audience"
752
- String variationId1 = "10418551353" ;
767
+ String variationId = "10418551353" ;
753
768
String variationId2 = "10418510624" ;
754
769
String variationKey1 = "variation_with_traffic" ;
755
770
String variationKey2 = "variation_no_traffic" ;
@@ -1786,6 +1801,8 @@ public void fetchQualifiedSegmentsAsync() throws InterruptedException {
1786
1801
.withODPManager (mockODPManager )
1787
1802
.build ();
1788
1803
1804
+
1805
+
1789
1806
OptimizelyUserContext userContext = optimizely .createUserContext ("test-user" );
1790
1807
1791
1808
CountDownLatch countDownLatch = new CountDownLatch (1 );
@@ -2084,4 +2101,42 @@ OptimizelyDecision callDecideWithIncludeReasons(String flagKey) {
2084
2101
return callDecideWithIncludeReasons (flagKey , Collections .emptyMap ());
2085
2102
}
2086
2103
2104
+ @ Test
2105
+ public void decide_holdoutApplied_basic () throws Exception {
2106
+ Optimizely optWithHoldout = createOptimizelyWithHoldouts ();
2107
+ // pick a flag that is eligible for basic_holdout. Using boolean_feature from config.
2108
+ String flagKey = "boolean_feature" ;
2109
+ String userId = "user123" ;
2110
+ Map <String , Object > attrs = new HashMap <>();
2111
+ attrs .put ("$opt_bucketing_id" , "ppid160000" );
2112
+ OptimizelyUserContext user = optWithHoldout .createUserContext (userId , attrs );
2113
+
2114
+ // include reasons to surface holdout logs in decision reasons if implemented
2115
+ OptimizelyDecision decision = user .decide (flagKey , Collections .singletonList (OptimizelyDecideOption .INCLUDE_REASONS ));
2116
+
2117
+ assertNotNull (decision );
2118
+ assertEquals (flagKey , decision .getFlagKey ());
2119
+ // holdout off variation => feature should be disabled
2120
+ assertFalse (decision .getEnabled ());
2121
+ // Expect decision source to be holdout (either via metadata map or reasons text)
2122
+ boolean hasHoldoutReason = false ;
2123
+ String expectedMString = "User (" + userId + ") is in variation (ho_off_key) of holdout (basic_holdout)." ;
2124
+ if (decision .getReasons ().contains (expectedMString )) {
2125
+ hasHoldoutReason = true ;
2126
+ }
2127
+ assertTrue ("Expected holdout to influence decision (reason containing 'holdout')" , hasHoldoutReason );
2128
+ logbackVerifier .expectMessage (Level .INFO , expectedMString );
2129
+
2130
+ // Impression expectation: Holdout decisions still dispatch an impression with holdout context.
2131
+ String variationId = "$opt_dummy_variation_id" ; // from holdouts-project-config.json
2132
+ String experimentId = "10075323428" ; // holdout id for basic_holdout
2133
+ DecisionMetadata metadata = new DecisionMetadata .Builder ()
2134
+ .setFlagKey (flagKey )
2135
+ .setRuleKey ("basic_holdout" )
2136
+ .setRuleType ("holdout" )
2137
+ .setVariationKey ("ho_off_key" )
2138
+ .setEnabled (false )
2139
+ .build ();
2140
+ eventHandler .expectImpression (experimentId , variationId , userId , Collections .emptyMap (), metadata );
2141
+ }
2087
2142
}
0 commit comments