Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 1612b1f

Browse files
authored
Merge pull request #55 from launchdarkly/eb/flag-tests
add more flag evaluation unit tests
2 parents 3a1d855 + be75e5f commit 1612b1f

File tree

3 files changed

+263
-71
lines changed

3 files changed

+263
-71
lines changed

src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.gson.JsonElement;
44

55
import java.util.ArrayList;
6+
import java.util.Arrays;
67
import java.util.List;
78

89
class FeatureFlagBuilder {
@@ -84,6 +85,10 @@ FeatureFlagBuilder variations(List<JsonElement> variations) {
8485
return this;
8586
}
8687

88+
FeatureFlagBuilder variations(JsonElement... variations) {
89+
return variations(Arrays.asList(variations));
90+
}
91+
8792
FeatureFlagBuilder deleted(boolean deleted) {
8893
this.deleted = deleted;
8994
return this;
Lines changed: 223 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
package com.launchdarkly.client;
22

3-
import com.google.gson.JsonElement;
4-
import com.google.gson.JsonPrimitive;
5-
6-
import org.junit.Assert;
73
import org.junit.Before;
84
import org.junit.Test;
95

106
import java.util.Arrays;
117

8+
import static com.launchdarkly.client.TestUtil.booleanFlagWithClauses;
9+
import static com.launchdarkly.client.TestUtil.fallthroughVariation;
10+
import static com.launchdarkly.client.TestUtil.jbool;
11+
import static com.launchdarkly.client.TestUtil.jint;
12+
import static com.launchdarkly.client.TestUtil.js;
1213
import static com.launchdarkly.client.VersionedDataKind.FEATURES;
1314
import static com.launchdarkly.client.VersionedDataKind.SEGMENTS;
14-
import static java.util.Collections.singletonList;
15+
import static org.junit.Assert.assertEquals;
16+
import static org.junit.Assert.assertNull;
1517

1618
public class FeatureFlagTest {
1719

20+
private static LDUser BASE_USER = new LDUser.Builder("x").build();
21+
1822
private FeatureStore featureStore;
1923

2024
@Before
@@ -23,51 +27,222 @@ public void before() {
2327
}
2428

2529
@Test
26-
public void testPrereqDoesNotExist() throws EvaluationException {
27-
String keyA = "keyA";
28-
String keyB = "keyB";
29-
FeatureFlag f1 = newFlagWithPrereq(keyA, keyB);
30-
31-
featureStore.upsert(FEATURES, f1);
32-
LDUser user = new LDUser.Builder("userKey").build();
33-
FeatureFlag.EvalResult actual = f1.evaluate(user, featureStore);
34-
35-
Assert.assertNull(actual.getValue());
36-
Assert.assertNotNull(actual.getPrerequisiteEvents());
37-
Assert.assertEquals(0, actual.getPrerequisiteEvents().size());
30+
public void flagReturnsOffVariationIfFlagIsOff() throws Exception {
31+
FeatureFlag f = new FeatureFlagBuilder("feature")
32+
.on(false)
33+
.offVariation(1)
34+
.fallthrough(fallthroughVariation(0))
35+
.variations(js("fall"), js("off"), js("on"))
36+
.build();
37+
FeatureFlag.EvalResult result = f.evaluate(BASE_USER, featureStore);
38+
39+
assertEquals(js("off"), result.getValue());
40+
assertEquals(0, result.getPrerequisiteEvents().size());
3841
}
3942

4043
@Test
41-
public void testPrereqCollectsEventsForPrereqs() throws EvaluationException {
42-
String keyA = "keyA";
43-
String keyB = "keyB";
44-
String keyC = "keyC";
45-
FeatureFlag flagA = newFlagWithPrereq(keyA, keyB);
46-
FeatureFlag flagB = newFlagWithPrereq(keyB, keyC);
47-
FeatureFlag flagC = newFlagOff(keyC);
48-
49-
featureStore.upsert(FEATURES, flagA);
50-
featureStore.upsert(FEATURES, flagB);
51-
featureStore.upsert(FEATURES, flagC);
52-
53-
LDUser user = new LDUser.Builder("userKey").build();
44+
public void flagReturnsNullIfFlagIsOffAndOffVariationIsUnspecified() throws Exception {
45+
FeatureFlag f = new FeatureFlagBuilder("feature")
46+
.on(false)
47+
.fallthrough(fallthroughVariation(0))
48+
.variations(js("fall"), js("off"), js("on"))
49+
.build();
50+
FeatureFlag.EvalResult result = f.evaluate(BASE_USER, featureStore);
51+
52+
assertNull(result.getValue());
53+
assertEquals(0, result.getPrerequisiteEvents().size());
54+
}
55+
56+
@Test
57+
public void flagReturnsOffVariationIfPrerequisiteIsNotFound() throws Exception {
58+
FeatureFlag f0 = new FeatureFlagBuilder("feature0")
59+
.on(true)
60+
.prerequisites(Arrays.asList(new Prerequisite("feature1", 1)))
61+
.fallthrough(fallthroughVariation(0))
62+
.offVariation(1)
63+
.variations(js("fall"), js("off"), js("on"))
64+
.build();
65+
FeatureFlag.EvalResult result = f0.evaluate(BASE_USER, featureStore);
66+
67+
assertEquals(js("off"), result.getValue());
68+
assertEquals(0, result.getPrerequisiteEvents().size());
69+
}
70+
71+
@Test
72+
public void flagReturnsOffVariationAndEventIfPrerequisiteIsNotMet() throws Exception {
73+
FeatureFlag f0 = new FeatureFlagBuilder("feature0")
74+
.on(true)
75+
.prerequisites(Arrays.asList(new Prerequisite("feature1", 1)))
76+
.fallthrough(fallthroughVariation(0))
77+
.offVariation(1)
78+
.variations(js("fall"), js("off"), js("on"))
79+
.version(1)
80+
.build();
81+
FeatureFlag f1 = new FeatureFlagBuilder("feature1")
82+
.on(true)
83+
.fallthrough(fallthroughVariation(0))
84+
.variations(js("nogo"), js("go"))
85+
.version(2)
86+
.build();
87+
featureStore.upsert(FEATURES, f1);
88+
FeatureFlag.EvalResult result = f0.evaluate(BASE_USER, featureStore);
89+
90+
assertEquals(js("off"), result.getValue());
91+
92+
assertEquals(1, result.getPrerequisiteEvents().size());
93+
FeatureRequestEvent event = result.getPrerequisiteEvents().get(0);
94+
assertEquals(f1.getKey(), event.key);
95+
assertEquals("feature", event.kind);
96+
assertEquals(js("nogo"), event.value);
97+
assertEquals(f1.getVersion(), event.version.intValue());
98+
assertEquals(f0.getKey(), event.prereqOf);
99+
}
54100

55-
FeatureFlag.EvalResult flagAResult = flagA.evaluate(user, featureStore);
56-
Assert.assertNotNull(flagAResult);
57-
Assert.assertNull(flagAResult.getValue());
58-
Assert.assertEquals(2, flagAResult.getPrerequisiteEvents().size());
101+
@Test
102+
public void flagReturnsFallthroughVariationAndEventIfPrerequisiteIsMetAndThereAreNoRules() throws Exception {
103+
FeatureFlag f0 = new FeatureFlagBuilder("feature0")
104+
.on(true)
105+
.prerequisites(Arrays.asList(new Prerequisite("feature1", 1)))
106+
.fallthrough(fallthroughVariation(0))
107+
.offVariation(1)
108+
.variations(js("fall"), js("off"), js("on"))
109+
.version(1)
110+
.build();
111+
FeatureFlag f1 = new FeatureFlagBuilder("feature1")
112+
.on(true)
113+
.fallthrough(fallthroughVariation(1))
114+
.variations(js("nogo"), js("go"))
115+
.version(2)
116+
.build();
117+
featureStore.upsert(FEATURES, f1);
118+
FeatureFlag.EvalResult result = f0.evaluate(BASE_USER, featureStore);
119+
120+
assertEquals(js("fall"), result.getValue());
121+
assertEquals(1, result.getPrerequisiteEvents().size());
122+
123+
FeatureRequestEvent event = result.getPrerequisiteEvents().get(0);
124+
assertEquals(f1.getKey(), event.key);
125+
assertEquals("feature", event.kind);
126+
assertEquals(js("go"), event.value);
127+
assertEquals(f1.getVersion(), event.version.intValue());
128+
assertEquals(f0.getKey(), event.prereqOf);
129+
}
59130

60-
FeatureFlag.EvalResult flagBResult = flagB.evaluate(user, featureStore);
61-
Assert.assertNotNull(flagBResult);
62-
Assert.assertNull(flagBResult.getValue());
63-
Assert.assertEquals(1, flagBResult.getPrerequisiteEvents().size());
131+
@Test
132+
public void multipleLevelsOfPrerequisitesProduceMultipleEvents() throws Exception {
133+
FeatureFlag f0 = new FeatureFlagBuilder("feature0")
134+
.on(true)
135+
.prerequisites(Arrays.asList(new Prerequisite("feature1", 1)))
136+
.fallthrough(fallthroughVariation(0))
137+
.offVariation(1)
138+
.variations(js("fall"), js("off"), js("on"))
139+
.version(1)
140+
.build();
141+
FeatureFlag f1 = new FeatureFlagBuilder("feature1")
142+
.on(true)
143+
.prerequisites(Arrays.asList(new Prerequisite("feature2", 1)))
144+
.fallthrough(fallthroughVariation(1))
145+
.variations(js("nogo"), js("go"))
146+
.version(2)
147+
.build();
148+
FeatureFlag f2 = new FeatureFlagBuilder("feature2")
149+
.on(true)
150+
.fallthrough(fallthroughVariation(1))
151+
.variations(js("nogo"), js("go"))
152+
.version(3)
153+
.build();
154+
featureStore.upsert(FEATURES, f1);
155+
featureStore.upsert(FEATURES, f2);
156+
FeatureFlag.EvalResult result = f0.evaluate(BASE_USER, featureStore);
157+
158+
assertEquals(js("fall"), result.getValue());
159+
assertEquals(2, result.getPrerequisiteEvents().size());
160+
161+
FeatureRequestEvent event0 = result.getPrerequisiteEvents().get(0);
162+
assertEquals(f2.getKey(), event0.key);
163+
assertEquals("feature", event0.kind);
164+
assertEquals(js("go"), event0.value);
165+
assertEquals(f2.getVersion(), event0.version.intValue());
166+
assertEquals(f1.getKey(), event0.prereqOf);
64167

65-
FeatureFlag.EvalResult flagCResult = flagC.evaluate(user, featureStore);
66-
Assert.assertNotNull(flagCResult);
67-
Assert.assertEquals(null, flagCResult.getValue());
68-
Assert.assertEquals(0, flagCResult.getPrerequisiteEvents().size());
168+
FeatureRequestEvent event1 = result.getPrerequisiteEvents().get(1);
169+
assertEquals(f1.getKey(), event1.key);
170+
assertEquals("feature", event1.kind);
171+
assertEquals(js("go"), event1.value);
172+
assertEquals(f1.getVersion(), event1.version.intValue());
173+
assertEquals(f0.getKey(), event1.prereqOf);
69174
}
70-
175+
176+
@Test
177+
public void flagMatchesUserFromTargets() throws Exception {
178+
FeatureFlag f = new FeatureFlagBuilder("feature")
179+
.on(true)
180+
.targets(Arrays.asList(new Target(Arrays.asList("whoever", "userkey"), 2)))
181+
.fallthrough(fallthroughVariation(0))
182+
.offVariation(1)
183+
.variations(js("fall"), js("off"), js("on"))
184+
.build();
185+
LDUser user = new LDUser.Builder("userkey").build();
186+
FeatureFlag.EvalResult result = f.evaluate(user, featureStore);
187+
188+
assertEquals(js("on"), result.getValue());
189+
assertEquals(0, result.getPrerequisiteEvents().size());
190+
}
191+
192+
@Test
193+
public void flagMatchesUserFromRules() throws Exception {
194+
Clause clause = new Clause("key", Operator.in, Arrays.asList(js("userkey")), false);
195+
Rule rule = new Rule(Arrays.asList(clause), 2, null);
196+
FeatureFlag f = new FeatureFlagBuilder("feature")
197+
.on(true)
198+
.rules(Arrays.asList(rule))
199+
.fallthrough(fallthroughVariation(0))
200+
.offVariation(1)
201+
.variations(js("fall"), js("off"), js("on"))
202+
.build();
203+
LDUser user = new LDUser.Builder("userkey").build();
204+
FeatureFlag.EvalResult result = f.evaluate(user, featureStore);
205+
206+
assertEquals(js("on"), result.getValue());
207+
assertEquals(0, result.getPrerequisiteEvents().size());
208+
}
209+
210+
@Test
211+
public void clauseCanMatchBuiltInAttribute() throws Exception {
212+
Clause clause = new Clause("name", Operator.in, Arrays.asList(js("Bob")), false);
213+
FeatureFlag f = booleanFlagWithClauses(clause);
214+
LDUser user = new LDUser.Builder("key").name("Bob").build();
215+
216+
assertEquals(jbool(true), f.evaluate(user, featureStore).getValue());
217+
}
218+
219+
@Test
220+
public void clauseCanMatchCustomAttribute() throws Exception {
221+
Clause clause = new Clause("legs", Operator.in, Arrays.asList(jint(4)), false);
222+
FeatureFlag f = booleanFlagWithClauses(clause);
223+
LDUser user = new LDUser.Builder("key").custom("legs", 4).build();
224+
225+
assertEquals(jbool(true), f.evaluate(user, featureStore).getValue());
226+
}
227+
228+
@Test
229+
public void clauseReturnsFalseForMissingAttribute() throws Exception {
230+
Clause clause = new Clause("legs", Operator.in, Arrays.asList(jint(4)), false);
231+
FeatureFlag f = booleanFlagWithClauses(clause);
232+
LDUser user = new LDUser.Builder("key").name("Bob").build();
233+
234+
assertEquals(jbool(false), f.evaluate(user, featureStore).getValue());
235+
}
236+
237+
@Test
238+
public void clauseCanBeNegated() throws Exception {
239+
Clause clause = new Clause("name", Operator.in, Arrays.asList(js("Bob")), true);
240+
FeatureFlag f = booleanFlagWithClauses(clause);
241+
LDUser user = new LDUser.Builder("key").name("Bob").build();
242+
243+
assertEquals(jbool(false), f.evaluate(user, featureStore).getValue());
244+
}
245+
71246
@Test
72247
public void testSegmentMatchClauseRetrievesSegmentFromStore() throws Exception {
73248
Segment segment = new Segment.Builder("segkey")
@@ -80,7 +255,7 @@ public void testSegmentMatchClauseRetrievesSegmentFromStore() throws Exception {
80255
LDUser user = new LDUser.Builder("foo").build();
81256

82257
FeatureFlag.EvalResult result = flag.evaluate(user, featureStore);
83-
Assert.assertEquals(new JsonPrimitive(true), result.getValue());
258+
assertEquals(jbool(true), result.getValue());
84259
}
85260

86261
@Test
@@ -89,34 +264,11 @@ public void testSegmentMatchClauseFallsThroughIfSegmentNotFound() throws Excepti
89264
LDUser user = new LDUser.Builder("foo").build();
90265

91266
FeatureFlag.EvalResult result = flag.evaluate(user, featureStore);
92-
Assert.assertEquals(new JsonPrimitive(false), result.getValue());
93-
}
94-
95-
private FeatureFlag newFlagWithPrereq(String featureKey, String prereqKey) {
96-
return new FeatureFlagBuilder(featureKey)
97-
.prerequisites(singletonList(new Prerequisite(prereqKey, 0)))
98-
.variations(Arrays.<JsonElement>asList(new JsonPrimitive(0), new JsonPrimitive(1)))
99-
.fallthrough(new VariationOrRollout(0, null))
100-
.on(true)
101-
.build();
267+
assertEquals(jbool(false), result.getValue());
102268
}
103-
104-
private FeatureFlag newFlagOff(String featureKey) {
105-
return new FeatureFlagBuilder(featureKey)
106-
.variations(Arrays.<JsonElement>asList(new JsonPrimitive(0), new JsonPrimitive(1)))
107-
.fallthrough(new VariationOrRollout(0, null))
108-
.on(false)
109-
.build();
110-
}
111-
269+
112270
private FeatureFlag segmentMatchBooleanFlag(String segmentKey) {
113-
Clause clause = new Clause("", Operator.segmentMatch, Arrays.asList(new JsonPrimitive(segmentKey)), false);
114-
Rule rule = new Rule(Arrays.asList(clause), 1, null);
115-
return new FeatureFlagBuilder("key")
116-
.variations(Arrays.<JsonElement>asList(new JsonPrimitive(false), new JsonPrimitive(true)))
117-
.fallthrough(new VariationOrRollout(0, null))
118-
.on(true)
119-
.rules(Arrays.asList(rule))
120-
.build();
271+
Clause clause = new Clause("", Operator.segmentMatch, Arrays.asList(js(segmentKey)), false);
272+
return booleanFlagWithClauses(clause);
121273
}
122274
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.launchdarkly.client;
2+
3+
import com.google.gson.JsonPrimitive;
4+
5+
import java.util.Arrays;
6+
7+
public class TestUtil {
8+
9+
public static JsonPrimitive js(String s) {
10+
return new JsonPrimitive(s);
11+
}
12+
13+
public static JsonPrimitive jint(int n) {
14+
return new JsonPrimitive(n);
15+
}
16+
17+
public static JsonPrimitive jbool(boolean b) {
18+
return new JsonPrimitive(b);
19+
}
20+
21+
public static VariationOrRollout fallthroughVariation(int variation) {
22+
return new VariationOrRollout(variation, null);
23+
}
24+
25+
public static FeatureFlag booleanFlagWithClauses(Clause... clauses) {
26+
Rule rule = new Rule(Arrays.asList(clauses), 1, null);
27+
return new FeatureFlagBuilder("feature")
28+
.on(true)
29+
.rules(Arrays.asList(rule))
30+
.fallthrough(fallthroughVariation(0))
31+
.offVariation(0)
32+
.variations(jbool(false), jbool(true))
33+
.build();
34+
}
35+
}

0 commit comments

Comments
 (0)