diff --git a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientTest.java b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientTest.java index 9fbbb9602..232a0dfed 100644 --- a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientTest.java +++ b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientTest.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2017-2019, Optimizely, Inc. and contributors * + * Copyright 2017-2020, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -19,6 +19,9 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.optimizely.ab.Optimizely; import com.optimizely.ab.android.event_handler.DefaultEventHandler; import com.optimizely.ab.bucketing.Bucketer; @@ -26,6 +29,7 @@ import com.optimizely.ab.config.Experiment; import com.optimizely.ab.config.ProjectConfig; import com.optimizely.ab.config.Variation; +import com.optimizely.ab.config.parser.JsonParseException; import com.optimizely.ab.event.EventHandler; import com.optimizely.ab.event.LogEvent; import com.optimizely.ab.internal.ReservedEventKey; @@ -37,6 +41,7 @@ import com.optimizely.ab.notification.TrackNotification; import com.optimizely.ab.notification.TrackNotificationListener; import com.optimizely.ab.optimizelyconfig.OptimizelyConfig; +import com.optimizely.ab.optimizelyjson.OptimizelyJSON; import org.junit.Assert; import org.junit.Test; @@ -105,6 +110,7 @@ public static Collection data() throws IOException { private final static String INTEGER_VARIABLE_KEY = "integer_variable"; private final static String STRING_FEATURE_KEY = "multi_variate_feature"; private final static String STRING_VARIABLE_KEY = "first_letter"; + private final static String JSON_VARIABLE_KEY = "json_patched"; private static final String GENERIC_USER_ID = "userId"; private String testProjectId = "7595190003"; private int datafileVersion; @@ -1764,6 +1770,239 @@ public void testBadGetFeatureVariableString() { ); } + /* + * FeatureVariableJSON + * Scenario#1 Without attributes in which user + * was not bucketed into any variation for feature flag will return default value which is + * '{"k1":"v1","k2":3.5,"k3":true,"k4":{"kk1":"vv1","kk2":false}}' in config + */ + @Test + public void testGetFeatureVariableJSONWithoutAttr() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger + ); + String defaultValueOfStringVar = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true,\"k4\":{\"kk1\":\"vv1\",\"kk2\":false}}"; + + OptimizelyJSON json = optimizelyClient.getFeatureVariableJSON( + STRING_FEATURE_KEY, + JSON_VARIABLE_KEY, + GENERIC_USER_ID + ); + + assertTrue(compareJsonStrings(json.toString(), defaultValueOfStringVar)); + } + + //FeatureVariableJSON Scenario#2 with attributes + @Test + public void testGetFeatureVariableJsonWithAttr() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger); + + + String defaultValueOfStringVar = "{\"k1\":\"s1\",\"k2\":103.5,\"k3\":false,\"k4\":{\"kk1\":\"ss1\",\"kk2\":true}}"; + + OptimizelyJSON json = optimizelyClient.getFeatureVariableJSON( + STRING_FEATURE_KEY, + JSON_VARIABLE_KEY, + GENERIC_USER_ID, + Collections.singletonMap("house", "Gryffindor") + ); + + assertTrue(compareJsonStrings(json.toString(), defaultValueOfStringVar)); + verifyZeroInteractions(logger); + } + + //FeatureVariableJSON Scenario#3 if feature not found + @Test + public void testGetFeatureVariableJsonInvalidFeatureKey() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger); + + assertNull(optimizelyClient.getFeatureVariableJSON( + "invalidFeatureKey", + "invalidVariableKey", + GENERIC_USER_ID + )); + } + + //FeatureVariableJSON Scenario#4 if variable not found + @Test + public void testGetFeatureVariableJsonInvalidVariableKey() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger + ); + + //Scenario#4 if variable not found + assertNull(optimizelyClient.getFeatureVariableJSON( + STRING_FEATURE_KEY, + "invalidVariableKey", + GENERIC_USER_ID + )); + } + + @Test + public void testBadGetFeatureVariableJson() { + OptimizelyClient optimizelyClient = new OptimizelyClient(null, logger); + + //Scenario#1 without attributes + assertNull(optimizelyClient.getFeatureVariableJSON( + STRING_FEATURE_KEY, + JSON_VARIABLE_KEY, + GENERIC_USER_ID + )); + + verify(logger).warn("Optimizely is not initialized, could not get feature {} variable {} JSON for user {}.", + STRING_FEATURE_KEY, + JSON_VARIABLE_KEY, + GENERIC_USER_ID + ); + + //Scenario#2 with attributes + assertNull(optimizelyClient.getFeatureVariableJSON( + STRING_FEATURE_KEY, + JSON_VARIABLE_KEY, + GENERIC_USER_ID, + Collections.EMPTY_MAP + )); + + verify(logger).warn("Optimizely is not initialized, could not get feature {} variable {} JSON for user {} with attributes.", + STRING_FEATURE_KEY, + JSON_VARIABLE_KEY, + GENERIC_USER_ID + ); + } + + /* + * getAllFeatureVariables + * Scenario#1 Without attributes in which user + * was not bucketed into any variation for feature flag will return default value which is + * '{"first_letter":"H","json_patched":{"k1":"v1","k2":3.5,"k3":true,"k4":{"kk1":"vv1","kk2":false}},"rest_of_name":"arry"}' in config + */ + @Test + public void testGetAllFeatureVariablesWithoutAttr() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger + ); + String defaultValueOfStringVar = "{\"first_letter\":\"H\",\"json_patched\":{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true,\"k4\":{\"kk1\":\"vv1\",\"kk2\":false}},\"rest_of_name\":\"arry\"}"; + + OptimizelyJSON json = optimizelyClient.getAllFeatureVariables( + STRING_FEATURE_KEY, + GENERIC_USER_ID + ); + + assertTrue(compareJsonStrings(json.toString(), defaultValueOfStringVar)); + } + + //GetAllFeatureVariables Scenario#2 with attributes + @Test + public void testGetAllFeatureVariablesWithAttr() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger); + + + String defaultValueOfStringVar = "{\"first_letter\":\"F\",\"json_patched\":{\"k1\":\"s1\",\"k2\":103.5,\"k3\":false,\"k4\":{\"kk1\":\"ss1\",\"kk2\":true}},\"rest_of_name\":\"eorge\"}"; + + OptimizelyJSON json = optimizelyClient.getAllFeatureVariables( + STRING_FEATURE_KEY, + GENERIC_USER_ID, + Collections.singletonMap("house", "Gryffindor") + ); + + assertTrue(compareJsonStrings(json.toString(), defaultValueOfStringVar)); + verifyZeroInteractions(logger); + } + + //GetAllFeatureVariables Scenario#3 if feature not found + @Test + public void testGetAllFeatureVariablesInvalidFeatureKey() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger); + + assertNull(optimizelyClient.getAllFeatureVariables( + "invalidFeatureKey", + GENERIC_USER_ID + )); + } + + @Test + public void testBadGetAllFeatureVariables() { + OptimizelyClient optimizelyClient = new OptimizelyClient(null, logger); + + //Scenario#1 without attributes + assertNull(optimizelyClient.getAllFeatureVariables( + STRING_FEATURE_KEY, + GENERIC_USER_ID + )); + + verify(logger).warn("Optimizely is not initialized, could not get feature {} all feature variables for user {}.", + STRING_FEATURE_KEY, + GENERIC_USER_ID + ); + + //Scenario#2 with attributes + assertNull(optimizelyClient.getAllFeatureVariables( + STRING_FEATURE_KEY, + GENERIC_USER_ID, + Collections.EMPTY_MAP + )); + + verify(logger).warn("Optimizely is not initialized, could not get feature {} all feature variables for user {} with attributes.", + STRING_FEATURE_KEY, + GENERIC_USER_ID + ); + } + + // Accessibility testing of OptimizelyJSON.getValue + @Test + public void testGetValueOfOptimizelyJson() { + assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); + + Map expectedMap = new HashMap<>(); + expectedMap.put("kk1", "vv1"); + expectedMap.put("kk2", false); + + OptimizelyClient optimizelyClient = new OptimizelyClient( + optimizely, + logger + ); + + OptimizelyJSON optimizelyJSON = optimizelyClient.getAllFeatureVariables( + STRING_FEATURE_KEY, + GENERIC_USER_ID + ); + + try { + assertEquals(optimizelyJSON.getValue("first_letter", String.class), "H"); + assertEquals(optimizelyJSON.getValue("json_patched.k4", Map.class), expectedMap); + + // When given jsonKey does not exist + assertNull(optimizelyJSON.getValue("json_patched.k5", String.class)); + } catch (JsonParseException e) { + e.printStackTrace(); + } + } + @Test public void testGetOptimizelyConfig() { assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString())); @@ -1860,4 +2099,16 @@ public void handle(DecisionNotification decisionNotification) { assertTrue(optimizelyClient.getNotificationCenter().removeNotificationListener(notificationId)); assertFalse(optimizelyClient.getNotificationCenter().removeNotificationListener(notificationId2)); } + + private boolean compareJsonStrings(String str1, String str2) { + JsonParser parser = new JsonParser(); + + JsonElement j1 = parser.parse(str1); + JsonElement j2 = parser.parse(str2); + return j1.equals(j2); + } + + private Map parseJsonString(String str) { + return new Gson().fromJson(str, Map.class); + } } diff --git a/android-sdk/src/debug/res/raw/validprojectconfigv4 b/android-sdk/src/debug/res/raw/validprojectconfigv4 index f56bda720..13d9ef23f 100644 --- a/android-sdk/src/debug/res/raw/validprojectconfigv4 +++ b/android-sdk/src/debug/res/raw/validprojectconfigv4 @@ -169,10 +169,13 @@ }, { "id": "4111654444", "value": "3.14" - }, { + }, { "id": "593964691", "value": "2" - }] + }, { + "id": "4111661000", + "value": "{\"k1\":\"s1\",\"k2\":103.5,\"k3\":false,\"k4\":{\"kk1\":\"ss1\",\"kk2\":true}}" + }] }, { "id": "4204375027", "key": "Gred", @@ -477,7 +480,13 @@ "key": "rest_of_name", "type": "string", "defaultValue": "arry" - }] + }, { + "id": "4111661000", + "key": "json_patched", + "type": "string", + "subType": "json", + "defaultValue": "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true,\"k4\":{\"kk1\":\"vv1\",\"kk2\":false}}" + }] }, { "id": "3263342226", "key": "mutex_group_feature", diff --git a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClient.java b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClient.java index 970da5a03..f72484070 100644 --- a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClient.java +++ b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClient.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2017-2019, Optimizely, Inc. and contributors * + * Copyright 2017-2020, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -30,6 +30,7 @@ import com.optimizely.ab.notification.NotificationHandler; import com.optimizely.ab.notification.TrackNotification; import com.optimizely.ab.optimizelyconfig.OptimizelyConfig; +import com.optimizely.ab.optimizelyjson.OptimizelyJSON; import org.slf4j.Logger; @@ -671,6 +672,98 @@ String getFeatureVariableString(@NonNull String featureKey, } } + /** + * Get the JSON value of the specified variable in the feature. + * + * @param featureKey The unique key of the feature. + * @param variableKey The unique key of the variable. + * @param userId The ID of the user. + * @return An OptimizelyJSON instance for the JSON variable value. + * Null if the feature or variable could not be found. + */ + @Nullable + public OptimizelyJSON getFeatureVariableJSON(@NonNull String featureKey, + @NonNull String variableKey, + @NonNull String userId) { + if (isValid()) { + return optimizely.getFeatureVariableJSON(featureKey, variableKey, userId); + } else { + logger.warn("Optimizely is not initialized, could not get feature {} variable {} JSON for user {}.", + featureKey, variableKey, userId); + return null; + } + } + + /** + * Get the JSON value of the specified variable in the feature. + * + * @param featureKey The unique key of the feature. + * @param variableKey The unique key of the variable. + * @param userId The ID of the user. + * @param attributes The user's attributes. + * @return An OptimizelyJSON instance for the JSON variable value. + * Null if the feature or variable could not be found. + */ + @Nullable + public OptimizelyJSON getFeatureVariableJSON(@NonNull String featureKey, + @NonNull String variableKey, + @NonNull String userId, + @NonNull Map attributes) { + if (isValid()) { + return optimizely.getFeatureVariableJSON( + featureKey, + variableKey, + userId, + attributes); + } else { + logger.warn("Optimizely is not initialized, could not get feature {} variable {} JSON for user {} with attributes.", + featureKey, variableKey, userId); + return null; + } + } + + /** + * Get the values of all variables in the feature. + * + * @param featureKey The unique key of the feature. + * @param userId The ID of the user. + * @return An OptimizelyJSON instance for all variable values. + * Null if the feature could not be found. + */ + @Nullable + public OptimizelyJSON getAllFeatureVariables(@NonNull String featureKey, + @NonNull String userId) { + if (isValid()) { + return optimizely.getAllFeatureVariables(featureKey, userId); + } else { + logger.warn("Optimizely is not initialized, could not get feature {} all feature variables for user {}.", + featureKey, userId); + return null; + } + } + + /** + * Get the values of all variables in the feature. + * + * @param featureKey The unique key of the feature. + * @param userId The ID of the user. + * @param attributes The user's attributes. + * @return An OptimizelyJSON instance for all variable values. + * Null if the feature could not be found. + */ + @Nullable + public OptimizelyJSON getAllFeatureVariables(@NonNull String featureKey, + @NonNull String userId, + @NonNull Map attributes) { + if (isValid()) { + return optimizely.getAllFeatureVariables(featureKey, userId, attributes); + } else { + logger.warn("Optimizely is not initialized, could not get feature {} all feature variables for user {} with attributes.", + featureKey, userId); + return null; + } + } + /** * Get {@link OptimizelyConfig} containing experiments and features map * diff --git a/build.gradle b/build.gradle index a3186f43a..70c5b1378 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,8 @@ allprojects { repositories { jcenter() google() + // Uncomment the next line to use maven locally + //mavenLocal() } } @@ -53,7 +55,7 @@ ext { build_tools_version = "29.0.3" min_sdk_version = 14 target_sdk_version = 29 - java_core_ver = "3.4.3" + java_core_ver = "3.5.0" android_logger_ver = "1.3.6" jacksonversion= "2.9.9.1" support_annotations_ver = "24.2.1"