Skip to content

Commit 1821874

Browse files
(fix): added unit tests for leaf node parsing (#239)
* added unit tests for leaf node parsing * somehow missed a commit. remove print statements * added leaf parsing of audience condition unit test as well * added unit tests for parsing typedAudience leafs as well
1 parent cb7fa98 commit 1821874

File tree

11 files changed

+265
-23
lines changed

11 files changed

+265
-23
lines changed

core-api/src/main/java/com/optimizely/ab/config/parser/AudienceJacksonDeserializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public Audience deserialize(JsonParser parser, DeserializationContext context) t
4848
JsonNode conditionsJson = node.get("conditions");
4949
conditionsJson = objectMapper.readTree(conditionsJson.textValue());
5050

51-
Condition conditions = ConditionJacksonDeserializer.<UserAttribute>parseConditions(UserAttribute.class, objectMapper, conditionsJson);
51+
Condition conditions = ConditionJacksonDeserializer.<UserAttribute>parseCondition(UserAttribute.class, objectMapper, conditionsJson);
5252

5353
return new Audience(id, name, conditions);
5454
}

core-api/src/main/java/com/optimizely/ab/config/parser/ConditionJacksonDeserializer.java

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.optimizely.ab.config.audience.NullCondition;
3131
import com.optimizely.ab.config.audience.OrCondition;
3232
import com.optimizely.ab.config.audience.UserAttribute;
33+
import com.optimizely.ab.internal.ConditionUtils;
3334
import com.optimizely.ab.internal.InvalidAudienceCondition;
3435

3536
import java.io.IOException;
@@ -50,7 +51,7 @@ public ConditionJacksonDeserializer() {
5051
@Override
5152
public Condition deserialize(JsonParser parser, DeserializationContext context) throws IOException {
5253
JsonNode node = parser.getCodec().readTree(parser);
53-
Condition conditions = ConditionJacksonDeserializer.<AudienceIdCondition>parseConditions(AudienceIdCondition.class, objectMapper, node);
54+
Condition conditions = ConditionJacksonDeserializer.<AudienceIdCondition>parseCondition(AudienceIdCondition.class, objectMapper, node);
5455

5556
return conditions;
5657
}
@@ -69,10 +70,33 @@ private static String operand(JsonNode opNode) {
6970
}
7071
return null;
7172
}
73+
74+
protected static <T> Condition parseCondition(Class<T> clazz, ObjectMapper objectMapper,JsonNode conditionNode)
75+
throws JsonProcessingException, InvalidAudienceCondition {
76+
77+
if (conditionNode.isArray()) {
78+
return ConditionJacksonDeserializer.<T>parseConditions(clazz, objectMapper, conditionNode);
79+
} else if (conditionNode.isTextual()) {
80+
if (clazz != AudienceIdCondition.class) {
81+
throw new InvalidAudienceCondition(String.format("Expected AudienceIdCondition got %s", clazz.getCanonicalName()));
82+
83+
}
84+
return objectMapper.treeToValue(conditionNode, AudienceIdCondition.class);
85+
}
86+
else if (conditionNode.isObject()) {
87+
if (clazz != UserAttribute.class) {
88+
throw new InvalidAudienceCondition(String.format("Expected UserAttributes got %s", clazz.getCanonicalName()));
89+
90+
}
91+
return objectMapper.treeToValue(conditionNode, UserAttribute.class);
92+
}
93+
94+
return null;
95+
}
7296
protected static <T> Condition parseConditions(Class<T> clazz, ObjectMapper objectMapper, JsonNode conditionNode)
7397
throws JsonProcessingException, InvalidAudienceCondition {
7498

75-
if (conditionNode.size() == 0) {
99+
if (conditionNode.isArray() && conditionNode.size() == 0) {
76100
return new EmptyCondition();
77101
}
78102

@@ -89,22 +113,7 @@ protected static <T> Condition parseConditions(Class<T> clazz, ObjectMapper obje
89113

90114
for (int i = startingParsingIndex; i < conditionNode.size(); i++) {
91115
JsonNode subNode = conditionNode.get(i);
92-
if (subNode.isArray()) {
93-
conditions.add(ConditionJacksonDeserializer.<T>parseConditions(clazz, objectMapper, subNode));
94-
} else if (subNode.isTextual()) {
95-
if (clazz != AudienceIdCondition.class) {
96-
throw new InvalidAudienceCondition(String.format("Expected AudienceIdCondition got %s", clazz.getCanonicalName()));
97-
98-
}
99-
conditions.add(objectMapper.treeToValue(subNode, AudienceIdCondition.class));
100-
}
101-
else if (subNode.isObject()) {
102-
if (clazz != UserAttribute.class) {
103-
throw new InvalidAudienceCondition(String.format("Expected UserAttributes got %s", clazz.getCanonicalName()));
104-
105-
}
106-
conditions.add(objectMapper.treeToValue(subNode, UserAttribute.class));
107-
}
116+
conditions.add(ConditionJacksonDeserializer.<T>parseCondition(clazz, objectMapper, subNode));
108117
}
109118

110119
Condition condition;

core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,11 @@ static Condition parseAudienceConditions(JsonObject experimentJson) {
113113
List<Object> rawObjectList = gson.fromJson(conditionsElement, List.class);
114114
return ConditionUtils.<AudienceIdCondition>parseConditions(AudienceIdCondition.class, rawObjectList);
115115
}
116-
else if (conditionsElement.isJsonObject()) {
116+
else {
117117
Object jsonObject = gson.fromJson(conditionsElement,Object.class);
118118
return ConditionUtils.<AudienceIdCondition>parseConditions(AudienceIdCondition.class, jsonObject);
119119
}
120120

121-
return null;
122121
}
123122

124123
static Experiment parseExperiment(JsonObject experimentJson, String groupId, JsonDeserializationContext context) {

core-api/src/main/java/com/optimizely/ab/config/parser/TypedAudienceJacksonDeserializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public TypedAudience deserialize(JsonParser parser, DeserializationContext conte
3333

3434
JsonNode conditionsJson = node.get("conditions");
3535

36-
Condition conditions = ConditionJacksonDeserializer.<UserAttribute>parseConditions(UserAttribute.class, objectMapper, conditionsJson);
36+
Condition conditions = ConditionJacksonDeserializer.<UserAttribute>parseCondition(UserAttribute.class, objectMapper, conditionsJson);
3737

3838
return new TypedAudience(id, name, conditions);
3939
}

core-api/src/main/java/com/optimizely/ab/internal/ConditionUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
public class ConditionUtils {
3535

3636
static public <T> Condition parseConditions(Class<T> clazz, Object object) throws InvalidAudienceCondition {
37+
3738
if (object instanceof List) {
3839
List<Object> objectList = (List<Object>)object;
3940
return ConditionUtils.<T>parseConditions(clazz, objectList);

core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ public class ValidProjectConfigV4 {
215215
new AudienceIdCondition(AUDIENCE_INT_ID),
216216
new AudienceIdCondition(AUDIENCE_DOUBLE_ID)));
217217

218+
// audienceConditions
219+
private static final Condition AUDIENCE_COMBINATION_LEAF_CONDITION =
220+
new AudienceIdCondition(AUDIENCE_BOOL_ID);
221+
218222
// audienceConditions
219223
private static final Condition AUDIENCE_COMBINATION =
220224
new OrCondition(Arrays.<Condition>asList(
@@ -556,6 +560,47 @@ public class ValidProjectConfigV4 {
556560
)
557561
)
558562
);
563+
private static final String LAYER_TYPEDAUDIENCE_LEAF_EXPERIMENT_ID = "1630555629";
564+
private static final String EXPERIMENT_TYPEDAUDIENCE_LEAF_EXPERIMENT_ID = "1323241599";
565+
public static final String EXPERIMENT_TYPEDAUDIENCE_LEAF_EXPERIMENT_KEY = "typed_audience_experiment_leaf_condition";
566+
private static final String VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A_ID = "1423767505";
567+
private static final String VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A_KEY = "A";
568+
private static final Variation VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A = new Variation(
569+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A_ID,
570+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A_KEY,
571+
Collections.<LiveVariableUsageInstance>emptyList()
572+
);
573+
private static final String VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B_ID = "3433458317";
574+
private static final String VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B_KEY = "B";
575+
private static final Variation VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B = new Variation(
576+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B_ID,
577+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B_KEY,
578+
Collections.<LiveVariableUsageInstance>emptyList()
579+
);
580+
581+
private static final Experiment EXPERIMENT_TYPEDAUDIENCE_LEAF_EXPERIMENT = new Experiment(
582+
EXPERIMENT_TYPEDAUDIENCE_LEAF_EXPERIMENT_ID,
583+
EXPERIMENT_TYPEDAUDIENCE_LEAF_EXPERIMENT_KEY,
584+
Experiment.ExperimentStatus.RUNNING.toString(),
585+
LAYER_TYPEDAUDIENCE_LEAF_EXPERIMENT_ID,
586+
Collections.<String>emptyList(),
587+
AUDIENCE_COMBINATION_LEAF_CONDITION,
588+
ProjectConfigTestUtils.createListOfObjects(
589+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A,
590+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B
591+
),
592+
Collections.EMPTY_MAP,
593+
ProjectConfigTestUtils.createListOfObjects(
594+
new TrafficAllocation(
595+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_A_ID,
596+
5000
597+
),
598+
new TrafficAllocation(
599+
VARIATION_TYPEDAUDIENCE_LEAF_EXPERIMENT_VARIATION_B_ID,
600+
10000
601+
)
602+
)
603+
);
559604
private static final String LAYER_FIRST_GROUPED_EXPERIMENT_ID = "3301900159";
560605
private static final String EXPERIMENT_FIRST_GROUPED_EXPERIMENT_ID = "2738374745";
561606
private static final String EXPERIMENT_FIRST_GROUPED_EXPERIMENT_KEY = "first_grouped_experiment";
@@ -1282,6 +1327,7 @@ public static ProjectConfig generateValidProjectConfigV4() {
12821327
experiments.add(EXPERIMENT_BASIC_EXPERIMENT);
12831328
experiments.add(EXPERIMENT_TYPEDAUDIENCE_EXPERIMENT);
12841329
experiments.add(EXPERIMENT_TYPEDAUDIENCE_WITH_AND_EXPERIMENT);
1330+
experiments.add(EXPERIMENT_TYPEDAUDIENCE_LEAF_EXPERIMENT);
12851331
experiments.add(EXPERIMENT_MULTIVARIATE_EXPERIMENT);
12861332
experiments.add(EXPERIMENT_DOUBLE_FEATURE_EXPERIMENT);
12871333
experiments.add(EXPERIMENT_PAUSED_EXPERIMENT);

core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
*/
1717
package com.optimizely.ab.config.parser;
1818

19+
import com.google.gson.Gson;
1920
import com.google.gson.JsonArray;
2021
import com.google.gson.JsonElement;
2122
import com.google.gson.JsonObject;
2223
import com.google.gson.reflect.TypeToken;
2324
import com.optimizely.ab.config.ProjectConfig;
2425
import com.optimizely.ab.config.audience.Audience;
2526
import com.optimizely.ab.config.audience.Condition;
27+
import com.optimizely.ab.config.audience.TypedAudience;
2628
import com.optimizely.ab.internal.InvalidAudienceCondition;
2729
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2830
import org.junit.Rule;
@@ -82,7 +84,7 @@ public void parseAudience() throws Exception {
8284
jsonObject.addProperty("id", "123");
8385
jsonObject.addProperty("name","blah");
8486
jsonObject.addProperty("conditions",
85-
"[\"and\", [\"or\", [\"or\", {\"name\": \"doubleKey\", \"type\": \"custom_attribute\", \"match\":\"lt\", \"value\":100.0}]]]");
87+
"[\"and\", [\"or\", [\"or\", {\"name\": \"doubleKey\", \"type\": \"custom_attribute\", \"match\":\"exact\", \"value\":100.0}]]]");
8688

8789
AudienceGsonDeserializer deserializer = new AudienceGsonDeserializer();
8890
Type audienceType = new TypeToken<List<Audience>>() {}.getType();
@@ -93,6 +95,46 @@ public void parseAudience() throws Exception {
9395
assertNotNull(audience.getConditions());
9496
}
9597

98+
@Test
99+
public void parseAudienceLeaf() throws Exception {
100+
JsonObject jsonObject = new JsonObject();
101+
jsonObject.addProperty("id", "123");
102+
jsonObject.addProperty("name","blah");
103+
jsonObject.addProperty("conditions",
104+
"{\"name\": \"doubleKey\", \"type\": \"custom_attribute\", \"match\":\"exact\", \"value\":100.0}");
105+
106+
AudienceGsonDeserializer deserializer = new AudienceGsonDeserializer();
107+
Type audienceType = new TypeToken<List<Audience>>() {}.getType();
108+
109+
Audience audience = deserializer.deserialize(jsonObject, audienceType, null);
110+
111+
assertNotNull(audience);
112+
assertNotNull(audience.getConditions());
113+
}
114+
115+
@Test
116+
public void parseTypedAudienceLeaf() throws Exception {
117+
JsonObject jsonObject = new JsonObject();
118+
jsonObject.addProperty("id", "123");
119+
jsonObject.addProperty("name","blah");
120+
121+
JsonObject userAttribute = new JsonObject();
122+
userAttribute.addProperty("name","doubleKey");
123+
userAttribute.addProperty("type", "custom_attribute");
124+
userAttribute.addProperty("match", "lt");
125+
userAttribute.addProperty("value", 100.0);
126+
127+
jsonObject.add("conditions", userAttribute);
128+
129+
AudienceGsonDeserializer deserializer = new AudienceGsonDeserializer();
130+
Type audienceType = new TypeToken<List<TypedAudience>>() {}.getType();
131+
132+
Audience audience = deserializer.deserialize(jsonObject, audienceType, null);
133+
134+
assertNotNull(audience);
135+
assertNotNull(audience.getConditions());
136+
}
137+
96138
@Test
97139
public void parseInvalidAudience() throws Exception {
98140
thrown.expect(InvalidAudienceCondition.class);
@@ -126,6 +168,21 @@ public void parseAudienceConditions() throws Exception {
126168
assertNotNull(condition);
127169
}
128170

171+
@Test
172+
public void parseAudienceCondition() throws Exception {
173+
JsonObject jsonObject = new JsonObject();
174+
175+
Gson gson = new Gson();
176+
177+
178+
JsonElement leaf = gson.toJsonTree("1");
179+
180+
jsonObject.add("audienceConditions", leaf);
181+
Condition condition = GsonHelpers.parseAudienceConditions(jsonObject);
182+
183+
assertNotNull(condition);
184+
}
185+
129186
@Test
130187
public void parseInvalidAudienceConditions() throws Exception {
131188
thrown.expect(InvalidAudienceCondition.class);

core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.optimizely.ab.config.ProjectConfig;
2222
import com.optimizely.ab.config.audience.Audience;
2323
import com.optimizely.ab.config.audience.Condition;
24+
import com.optimizely.ab.config.audience.TypedAudience;
2425
import com.optimizely.ab.internal.InvalidAudienceCondition;
2526
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2627
import org.junit.Rule;
@@ -91,6 +92,46 @@ public void parseAudience() throws Exception {
9192
assertNotNull(audience.getConditions());
9293
}
9394

95+
@Test
96+
public void parseAudienceLeaf() throws Exception {
97+
String audienceString =
98+
"{" +
99+
"\"id\": \"3468206645\"," +
100+
"\"name\": \"DOUBLE\"," +
101+
"\"conditions\": \"{\\\"name\\\": \\\"doubleKey\\\", \\\"type\\\": \\\"custom_attribute\\\", \\\"match\\\":\\\"lt\\\", \\\"value\\\":100.0}\"" +
102+
"},";
103+
104+
ObjectMapper objectMapper = new ObjectMapper();
105+
SimpleModule module = new SimpleModule();
106+
module.addDeserializer(Audience.class, new AudienceJacksonDeserializer(objectMapper));
107+
module.addDeserializer(Condition.class, new ConditionJacksonDeserializer(objectMapper));
108+
objectMapper.registerModule(module);
109+
110+
Audience audience = objectMapper.readValue(audienceString, Audience.class);
111+
assertNotNull(audience);
112+
assertNotNull(audience.getConditions());
113+
}
114+
115+
@Test
116+
public void parseTypedAudienceLeaf() throws Exception {
117+
String audienceString =
118+
"{" +
119+
"\"id\": \"3468206645\"," +
120+
"\"name\": \"DOUBLE\"," +
121+
"\"conditions\": {\"name\": \"doubleKey\", \"type\": \"custom_attribute\", \"match\":\"lt\", \"value\":100.0}" +
122+
"},";
123+
124+
ObjectMapper objectMapper = new ObjectMapper();
125+
SimpleModule module = new SimpleModule();
126+
module.addDeserializer(TypedAudience.class, new TypedAudienceJacksonDeserializer(objectMapper));
127+
module.addDeserializer(Condition.class, new ConditionJacksonDeserializer(objectMapper));
128+
objectMapper.registerModule(module);
129+
130+
Audience audience = objectMapper.readValue(audienceString, TypedAudience.class);
131+
assertNotNull(audience);
132+
assertNotNull(audience.getConditions());
133+
}
134+
94135
@Test
95136
public void parseInvalidAudience() throws Exception {
96137
thrown.expect(InvalidAudienceCondition.class);
@@ -113,6 +154,20 @@ public void parseInvalidAudience() throws Exception {
113154
assertNotNull(audience.getConditions());
114155
}
115156

157+
@Test
158+
public void parseAudienceCondition() throws Exception {
159+
String conditionString = "\"123\"";
160+
161+
ObjectMapper objectMapper = new ObjectMapper();
162+
SimpleModule module = new SimpleModule();
163+
module.addDeserializer(Audience.class, new AudienceJacksonDeserializer(objectMapper));
164+
module.addDeserializer(Condition.class, new ConditionJacksonDeserializer(objectMapper));
165+
objectMapper.registerModule(module);
166+
167+
Condition condition = objectMapper.readValue(conditionString, Condition.class);
168+
assertNotNull(condition);
169+
}
170+
116171
@Test
117172
public void parseAudienceConditions() throws Exception {
118173
String conditionString =

core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ public void parseAudience() throws Exception {
8888
assertNotNull(condition);
8989
}
9090

91+
@Test
92+
public void parseAudienceLeaf() throws Exception {
93+
JSONObject jsonObject = new JSONObject();
94+
95+
jsonObject.append("id", "123");
96+
jsonObject.append("name","blah");
97+
jsonObject.append("conditions",
98+
"{\"name\": \"doubleKey\", \"type\": \"custom_attribute\", \"match\":\"lt\", \"value\":100.0}");
99+
100+
Condition<UserAttribute> condition = ConditionUtils.parseConditions(UserAttribute.class, new JSONObject("{\"name\": \"doubleKey\", \"type\": \"custom_attribute\", \"match\":\"lt\", \"value\":100.0}"));
101+
102+
assertNotNull(condition);
103+
}
104+
91105
@Test
92106
public void parseInvalidAudience() throws Exception {
93107
thrown.expect(InvalidAudienceCondition.class);
@@ -100,6 +114,14 @@ public void parseInvalidAudience() throws Exception {
100114
ConditionUtils.parseConditions(UserAttribute.class, new JSONArray("[\"and\", [\"or\", [\"or\", \"123\"]]]"));
101115
}
102116

117+
@Test
118+
public void parseAudienceCondition() throws Exception {
119+
String conditions = "1";
120+
121+
Condition condition = ConditionUtils.parseConditions(AudienceIdCondition.class, conditions);
122+
assertNotNull(condition);
123+
}
124+
103125
@Test
104126
public void parseAudienceConditions() throws Exception {
105127
JSONArray conditions = new JSONArray();

0 commit comments

Comments
 (0)