|
23 | 23 | import com.optimizely.ab.config.Attribute;
|
24 | 24 | import com.optimizely.ab.config.EventType;
|
25 | 25 | import com.optimizely.ab.config.Experiment;
|
| 26 | +import com.optimizely.ab.config.LiveVariable; |
26 | 27 | import com.optimizely.ab.config.LiveVariableUsageInstance;
|
27 | 28 | import com.optimizely.ab.config.ProjectConfig;
|
28 | 29 | import com.optimizely.ab.config.TrafficAllocation;
|
29 | 30 | import com.optimizely.ab.config.Variation;
|
| 31 | +import com.optimizely.ab.config.parser.ConfigParseException; |
30 | 32 | import com.optimizely.ab.error.ErrorHandler;
|
31 | 33 | import com.optimizely.ab.error.NoOpErrorHandler;
|
32 | 34 | import com.optimizely.ab.error.RaiseExceptionErrorHandler;
|
|
74 | 76 | import static com.optimizely.ab.config.ValidProjectConfigV4.EXPERIMENT_LAUNCHED_EXPERIMENT_KEY;
|
75 | 77 | import static com.optimizely.ab.config.ValidProjectConfigV4.EXPERIMENT_MULTIVARIATE_EXPERIMENT_KEY;
|
76 | 78 | import static com.optimizely.ab.config.ValidProjectConfigV4.EXPERIMENT_PAUSED_EXPERIMENT_KEY;
|
| 79 | +import static com.optimizely.ab.config.ValidProjectConfigV4.FEATURE_FLAG_MULTI_VARIATE_FEATURE; |
| 80 | +import static com.optimizely.ab.config.ValidProjectConfigV4.FEATURE_MULTI_VARIATE_FEATURE_KEY; |
| 81 | +import static com.optimizely.ab.config.ValidProjectConfigV4.FEATURE_SINGLE_VARIABLE_STRING_KEY; |
77 | 82 | import static com.optimizely.ab.config.ValidProjectConfigV4.MULTIVARIATE_EXPERIMENT_FORCED_VARIATION_USER_ID_GRED;
|
78 | 83 | import static com.optimizely.ab.config.ValidProjectConfigV4.PAUSED_EXPERIMENT_FORCED_VARIATION_USER_ID_CONTROL;
|
| 84 | +import static com.optimizely.ab.config.ValidProjectConfigV4.VARIABLE_FIRST_LETTER_DEFAULT_VALUE; |
| 85 | +import static com.optimizely.ab.config.ValidProjectConfigV4.VARIABLE_FIRST_LETTER_KEY; |
| 86 | +import static com.optimizely.ab.config.ValidProjectConfigV4.VARIABLE_STRING_VARIABLE_DEFAULT_VALUE; |
| 87 | +import static com.optimizely.ab.config.ValidProjectConfigV4.VARIABLE_STRING_VARIABLE_KEY; |
| 88 | +import static com.optimizely.ab.config.ValidProjectConfigV4.VARIATION_MULTIVARIATE_EXPERIMENT_GRED; |
79 | 89 | import static com.optimizely.ab.config.ValidProjectConfigV4.VARIATION_MULTIVARIATE_EXPERIMENT_GRED_KEY;
|
80 | 90 | import static com.optimizely.ab.event.LogEvent.RequestMethod;
|
81 | 91 | import static com.optimizely.ab.event.internal.EventBuilderV2Test.createExperimentVariationMap;
|
|
84 | 94 | import static org.hamcrest.CoreMatchers.is;
|
85 | 95 | import static org.hamcrest.CoreMatchers.not;
|
86 | 96 | import static org.hamcrest.MatcherAssert.assertThat;
|
87 |
| -import static org.hamcrest.Matchers.array; |
88 | 97 | import static org.hamcrest.Matchers.hasEntry;
|
89 | 98 | import static org.hamcrest.Matchers.hasKey;
|
90 | 99 | import static org.junit.Assert.assertEquals;
|
91 | 100 | import static org.junit.Assert.assertNotNull;
|
92 | 101 | import static org.junit.Assert.assertNull;
|
| 102 | +import static org.junit.Assume.assumeTrue; |
93 | 103 | import static org.mockito.Matchers.any;
|
94 |
| -import static org.mockito.Matchers.anyMap; |
95 | 104 | import static org.mockito.Matchers.anyMapOf;
|
96 | 105 | import static org.mockito.Matchers.anyString;
|
97 | 106 | import static org.mockito.Matchers.eq;
|
| 107 | +import static org.mockito.Mockito.doReturn; |
98 | 108 | import static org.mockito.Mockito.doThrow;
|
99 | 109 | import static org.mockito.Mockito.mock;
|
100 | 110 | import static org.mockito.Mockito.never;
|
@@ -2143,6 +2153,224 @@ public void clearNotificationListeners() throws Exception {
|
2143 | 2153 | .onEventTracked(eventKey, genericUserId, attributes, null, logEventToDispatch);
|
2144 | 2154 | }
|
2145 | 2155 |
|
| 2156 | + //======== Feature Accessor Tests ========// |
| 2157 | + |
| 2158 | + /** |
| 2159 | + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, LiveVariable.VariableType)} |
| 2160 | + * returns null and logs a message |
| 2161 | + * when it is called with a feature key that has no corresponding feature in the datafile. |
| 2162 | + * @throws ConfigParseException |
| 2163 | + */ |
| 2164 | + @Test |
| 2165 | + public void getFeatureVariableValueForTypeReturnsNullWhenFeatureNotFound() throws ConfigParseException { |
| 2166 | + |
| 2167 | + String invalidFeatureKey = "nonexistent feature key"; |
| 2168 | + String invalidVariableKey = "nonexistent variable key"; |
| 2169 | + Map<String, String> attributes = Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE); |
| 2170 | + |
| 2171 | + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) |
| 2172 | + .withConfig(validProjectConfig) |
| 2173 | + .withDecisionService(mockDecisionService) |
| 2174 | + .build(); |
| 2175 | + |
| 2176 | + String value = optimizely.getFeatureVariableValueForType( |
| 2177 | + invalidFeatureKey, |
| 2178 | + invalidVariableKey, |
| 2179 | + genericUserId, |
| 2180 | + Collections.<String, String>emptyMap(), |
| 2181 | + LiveVariable.VariableType.STRING); |
| 2182 | + assertNull(value); |
| 2183 | + |
| 2184 | + value = optimizely.getFeatureVariableString(invalidFeatureKey, invalidVariableKey, genericUserId, attributes); |
| 2185 | + assertNull(value); |
| 2186 | + |
| 2187 | + logbackVerifier.expectMessage(Level.INFO, |
| 2188 | + "No feature flag was found for key \"" + invalidFeatureKey + "\".", |
| 2189 | + times(2)); |
| 2190 | + |
| 2191 | + verify(mockDecisionService, never()).getVariation( |
| 2192 | + any(Experiment.class), |
| 2193 | + anyString(), |
| 2194 | + anyMapOf(String.class, String.class)); |
| 2195 | + } |
| 2196 | + |
| 2197 | + /** |
| 2198 | + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, LiveVariable.VariableType)} |
| 2199 | + * returns null and logs a message |
| 2200 | + * when the feature key is valid, but no variable could be found for the variable key in the feature. |
| 2201 | + * @throws ConfigParseException |
| 2202 | + */ |
| 2203 | + @Test |
| 2204 | + public void getFeatureVariableValueForTypeReturnsNullWhenVariableNotFoundInValidFeature() throws ConfigParseException { |
| 2205 | + assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())); |
| 2206 | + |
| 2207 | + String validFeatureKey = FEATURE_MULTI_VARIATE_FEATURE_KEY; |
| 2208 | + String invalidVariableKey = "nonexistent variable key"; |
| 2209 | + |
| 2210 | + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) |
| 2211 | + .withConfig(validProjectConfig) |
| 2212 | + .withDecisionService(mockDecisionService) |
| 2213 | + .build(); |
| 2214 | + |
| 2215 | + String value = optimizely.getFeatureVariableValueForType( |
| 2216 | + validFeatureKey, |
| 2217 | + invalidVariableKey, |
| 2218 | + genericUserId, |
| 2219 | + Collections.<String, String>emptyMap(), |
| 2220 | + LiveVariable.VariableType.STRING); |
| 2221 | + assertNull(value); |
| 2222 | + |
| 2223 | + logbackVerifier.expectMessage(Level.INFO, |
| 2224 | + "No feature variable was found for key \"" + invalidVariableKey + "\" in feature flag \"" + |
| 2225 | + validFeatureKey + "\"."); |
| 2226 | + |
| 2227 | + verify(mockDecisionService, never()).getVariation( |
| 2228 | + any(Experiment.class), |
| 2229 | + anyString(), |
| 2230 | + anyMapOf(String.class, String.class) |
| 2231 | + ); |
| 2232 | + } |
| 2233 | + |
| 2234 | + /** |
| 2235 | + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, LiveVariable.VariableType)} |
| 2236 | + * returns null when the variable's type does not match the type with which it was attempted to be accessed. |
| 2237 | + * @throws ConfigParseException |
| 2238 | + */ |
| 2239 | + @Test |
| 2240 | + public void getFeatureVariableValueReturnsNullWhenVariableTypeDoesNotMatch() throws ConfigParseException { |
| 2241 | + assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())); |
| 2242 | + |
| 2243 | + String validFeatureKey = FEATURE_MULTI_VARIATE_FEATURE_KEY; |
| 2244 | + String validVariableKey = VARIABLE_FIRST_LETTER_KEY; |
| 2245 | + |
| 2246 | + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) |
| 2247 | + .withConfig(validProjectConfig) |
| 2248 | + .withDecisionService(mockDecisionService) |
| 2249 | + .build(); |
| 2250 | + |
| 2251 | + String value = optimizely.getFeatureVariableValueForType( |
| 2252 | + validFeatureKey, |
| 2253 | + validVariableKey, |
| 2254 | + genericUserId, |
| 2255 | + Collections.<String, String>emptyMap(), |
| 2256 | + LiveVariable.VariableType.INTEGER |
| 2257 | + ); |
| 2258 | + assertNull(value); |
| 2259 | + |
| 2260 | + logbackVerifier.expectMessage( |
| 2261 | + Level.INFO, |
| 2262 | + "The feature variable \"" + validVariableKey + |
| 2263 | + "\" is actually of type \"" + LiveVariable.VariableType.STRING.toString() + |
| 2264 | + "\" type. You tried to access it as type \"" + LiveVariable.VariableType.INTEGER.toString() + |
| 2265 | + "\". Please use the appropriate feature variable accessor." |
| 2266 | + ); |
| 2267 | + } |
| 2268 | + |
| 2269 | + /** |
| 2270 | + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, LiveVariable.VariableType)} |
| 2271 | + * returns the String default value of a live variable |
| 2272 | + * when the feature is not attached to an experiment. |
| 2273 | + * @throws ConfigParseException |
| 2274 | + */ |
| 2275 | + @Test |
| 2276 | + public void getFeatureVariableValueForTypeReturnsDefaultValueWhenFeatureIsNotAttached() throws ConfigParseException { |
| 2277 | + assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())); |
| 2278 | + |
| 2279 | + String validFeatureKey = FEATURE_SINGLE_VARIABLE_STRING_KEY; |
| 2280 | + String validVariableKey = VARIABLE_STRING_VARIABLE_KEY; |
| 2281 | + String defaultValue = VARIABLE_STRING_VARIABLE_DEFAULT_VALUE; |
| 2282 | + Map<String, String> attributes = Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE); |
| 2283 | + |
| 2284 | + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) |
| 2285 | + .withConfig(validProjectConfig) |
| 2286 | + .build(); |
| 2287 | + |
| 2288 | + String value = optimizely.getFeatureVariableValueForType( |
| 2289 | + validFeatureKey, |
| 2290 | + validVariableKey, |
| 2291 | + genericUserId, |
| 2292 | + attributes, |
| 2293 | + LiveVariable.VariableType.STRING); |
| 2294 | + assertEquals(defaultValue, value); |
| 2295 | + |
| 2296 | + logbackVerifier.expectMessage( |
| 2297 | + Level.INFO, |
| 2298 | + "The feature flag \"" + validFeatureKey + "\" is not used in any experiments." |
| 2299 | + ); |
| 2300 | + } |
| 2301 | + |
| 2302 | + /** |
| 2303 | + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, LiveVariable.VariableType)} |
| 2304 | + * returns the String default value for a live variable |
| 2305 | + * when the feature is attached to an experiment, but the user is excluded from the experiment. |
| 2306 | + * @throws ConfigParseException |
| 2307 | + */ |
| 2308 | + @Test |
| 2309 | + public void getFeatureVariableValueReturnsDefaultValueWhenFeatureIsAttachedToOneExperimentButFailsTargeting() throws ConfigParseException { |
| 2310 | + assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())); |
| 2311 | + |
| 2312 | + String validFeatureKey = FEATURE_MULTI_VARIATE_FEATURE_KEY; |
| 2313 | + String validVariableKey = VARIABLE_FIRST_LETTER_KEY; |
| 2314 | + String expectedValue = VARIABLE_FIRST_LETTER_DEFAULT_VALUE; |
| 2315 | + |
| 2316 | + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) |
| 2317 | + .withConfig(validProjectConfig) |
| 2318 | + .build(); |
| 2319 | + |
| 2320 | + String valueWithImproperAttributes = optimizely.getFeatureVariableValueForType( |
| 2321 | + validFeatureKey, |
| 2322 | + validVariableKey, |
| 2323 | + genericUserId, |
| 2324 | + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, "Slytherin"), |
| 2325 | + LiveVariable.VariableType.STRING |
| 2326 | + ); |
| 2327 | + assertEquals(expectedValue, valueWithImproperAttributes); |
| 2328 | + |
| 2329 | + logbackVerifier.expectMessage( |
| 2330 | + Level.INFO, |
| 2331 | + "User \"" + genericUserId + |
| 2332 | + "\" was not bucketed into any variation for feature flag \"" + validFeatureKey + |
| 2333 | + "\". The default value is being returned." |
| 2334 | + ); |
| 2335 | + } |
| 2336 | + |
| 2337 | + /** |
| 2338 | + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, LiveVariable.VariableType)} |
| 2339 | + * returns the variable value of the variation the user is bucketed into |
| 2340 | + * if the variation is not null and the variable has a usage within the variation. |
| 2341 | + * @throws ConfigParseException |
| 2342 | + */ |
| 2343 | + @Test |
| 2344 | + public void getFeatureVariableValueReturnsVariationValueWhenUserGetsBucketedToVariation() throws ConfigParseException { |
| 2345 | + assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())); |
| 2346 | + |
| 2347 | + String validFeatureKey = FEATURE_MULTI_VARIATE_FEATURE_KEY; |
| 2348 | + String validVariableKey = VARIABLE_FIRST_LETTER_KEY; |
| 2349 | + LiveVariable variable = FEATURE_FLAG_MULTI_VARIATE_FEATURE.getVariableKeyToLiveVariableMap().get(validVariableKey); |
| 2350 | + String expectedValue = VARIATION_MULTIVARIATE_EXPERIMENT_GRED.getVariableIdToLiveVariableUsageInstanceMap().get(variable.getId()).getValue(); |
| 2351 | + |
| 2352 | + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) |
| 2353 | + .withConfig(validProjectConfig) |
| 2354 | + .withDecisionService(mockDecisionService) |
| 2355 | + .build(); |
| 2356 | + |
| 2357 | + doReturn(VARIATION_MULTIVARIATE_EXPERIMENT_GRED).when(mockDecisionService).getVariationForFeature( |
| 2358 | + FEATURE_FLAG_MULTI_VARIATE_FEATURE, |
| 2359 | + genericUserId, |
| 2360 | + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE) |
| 2361 | + ); |
| 2362 | + |
| 2363 | + String value = optimizely.getFeatureVariableValueForType( |
| 2364 | + validFeatureKey, |
| 2365 | + validVariableKey, |
| 2366 | + genericUserId, |
| 2367 | + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE), |
| 2368 | + LiveVariable.VariableType.STRING |
| 2369 | + ); |
| 2370 | + |
| 2371 | + assertEquals(expectedValue, value); |
| 2372 | + } |
| 2373 | + |
2146 | 2374 | //======== Helper methods ========//
|
2147 | 2375 |
|
2148 | 2376 | private Experiment createUnknownExperiment() {
|
|
0 commit comments