Skip to content

Commit 2e437df

Browse files
committed
adds TestDataV2Test
1 parent 9ec551c commit 2e437df

1 file changed

Lines changed: 315 additions & 0 deletions

File tree

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
package com.launchdarkly.sdk.server.integrations;
2+
3+
import com.google.common.collect.ImmutableMap;
4+
import com.launchdarkly.sdk.ContextKind;
5+
import com.launchdarkly.sdk.LDValue;
6+
import com.launchdarkly.sdk.server.DataModel;
7+
import com.launchdarkly.sdk.server.LDConfig;
8+
import com.launchdarkly.sdk.server.ModelBuilders;
9+
import com.launchdarkly.sdk.server.datasources.FDv2SourceResult;
10+
import com.launchdarkly.sdk.server.datasources.SelectorSource;
11+
import com.launchdarkly.sdk.server.datasources.Synchronizer;
12+
import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider.ErrorInfo;
13+
import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider.State;
14+
import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider;
15+
import com.launchdarkly.sdk.server.subsystems.ClientContext;
16+
import com.launchdarkly.sdk.server.subsystems.DataSourceBuildInputs;
17+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.ChangeSet;
18+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.ChangeSetType;
19+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.DataKind;
20+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.ItemDescriptor;
21+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.KeyedItems;
22+
23+
import com.launchdarkly.sdk.internal.fdv2.sources.Selector;
24+
import org.junit.Test;
25+
26+
import java.util.Map;
27+
import java.util.concurrent.BlockingQueue;
28+
import java.util.concurrent.CompletableFuture;
29+
import java.util.concurrent.LinkedBlockingQueue;
30+
import java.util.concurrent.TimeUnit;
31+
import java.util.function.Function;
32+
33+
import static com.google.common.collect.Iterables.get;
34+
import static com.launchdarkly.sdk.server.ModelBuilders.flagBuilder;
35+
import static com.launchdarkly.sdk.server.TestComponents.clientContext;
36+
import static com.launchdarkly.sdk.server.TestComponents.nullLogger;
37+
import static com.launchdarkly.sdk.server.TestComponents.sharedExecutor;
38+
import static com.launchdarkly.testhelpers.JsonAssertions.assertJsonEquals;
39+
import static org.hamcrest.MatcherAssert.assertThat;
40+
import static org.hamcrest.Matchers.emptyIterable;
41+
import static org.hamcrest.Matchers.equalTo;
42+
import static org.hamcrest.Matchers.is;
43+
import static org.hamcrest.Matchers.iterableWithSize;
44+
import static org.hamcrest.Matchers.not;
45+
import static org.hamcrest.Matchers.notNullValue;
46+
import static org.hamcrest.Matchers.nullValue;
47+
48+
@SuppressWarnings("javadoc")
49+
public class TestDataV2Test {
50+
private static final LDValue[] THREE_STRING_VALUES =
51+
new LDValue[] { LDValue.of("red"), LDValue.of("green"), LDValue.of("blue") };
52+
53+
private final CapturingDataSourceUpdates updates = new CapturingDataSourceUpdates();
54+
55+
private DataSourceBuildInputs dataSourceBuildInputs() {
56+
ClientContext context = clientContext("", new LDConfig.Builder().build(), updates);
57+
SelectorSource selectorSource = () -> Selector.EMPTY;
58+
return new DataSourceBuildInputs(
59+
nullLogger,
60+
0,
61+
updates,
62+
context.getServiceEndpoints(),
63+
context.getHttp(),
64+
sharedExecutor,
65+
null,
66+
selectorSource);
67+
}
68+
69+
@Test
70+
public void initializesWithEmptyData() throws Exception {
71+
TestDataV2 td = TestDataV2.synchronizer();
72+
Synchronizer sync = td.build(dataSourceBuildInputs());
73+
74+
FDv2SourceResult result = sync.next().get(5, TimeUnit.SECONDS);
75+
76+
assertThat(result.getResultType(), equalTo(FDv2SourceResult.ResultType.CHANGE_SET));
77+
ChangeSet<ItemDescriptor> changeSet = result.getChangeSet();
78+
assertThat(changeSet, notNullValue());
79+
assertThat(changeSet.getType(), equalTo(ChangeSetType.Full));
80+
assertThat(changeSet.getData(), iterableWithSize(1));
81+
assertThat(get(changeSet.getData(), 0).getKey(), equalTo(DataModel.FEATURES));
82+
assertThat(get(changeSet.getData(), 0).getValue().getItems(), emptyIterable());
83+
}
84+
85+
@Test
86+
public void initializesWithFlags() throws Exception {
87+
TestDataV2 td = TestDataV2.synchronizer();
88+
td.update(td.flag("flag1").on(true))
89+
.update(td.flag("flag2").on(false));
90+
91+
Synchronizer sync = td.build(dataSourceBuildInputs());
92+
FDv2SourceResult result = sync.next().get(5, TimeUnit.SECONDS);
93+
94+
assertThat(result.getResultType(), equalTo(FDv2SourceResult.ResultType.CHANGE_SET));
95+
ChangeSet<ItemDescriptor> changeSet = result.getChangeSet();
96+
assertThat(changeSet.getType(), equalTo(ChangeSetType.Full));
97+
assertThat(changeSet.getData(), iterableWithSize(1));
98+
assertThat(get(changeSet.getData(), 0).getKey(), equalTo(DataModel.FEATURES));
99+
assertThat(get(changeSet.getData(), 0).getValue().getItems(), iterableWithSize(2));
100+
101+
ModelBuilders.FlagBuilder expectedFlag1 = flagBuilder("flag1").version(1).salt("")
102+
.on(true).offVariation(1).fallthroughVariation(0).variations(true, false);
103+
ModelBuilders.FlagBuilder expectedFlag2 = flagBuilder("flag2").version(1).salt("")
104+
.on(false).offVariation(1).fallthroughVariation(0).variations(true, false);
105+
106+
Map<String, ItemDescriptor> flags = ImmutableMap.copyOf(get(changeSet.getData(), 0).getValue().getItems());
107+
ItemDescriptor flag1 = flags.get("flag1");
108+
ItemDescriptor flag2 = flags.get("flag2");
109+
assertThat(flag1, not(nullValue()));
110+
assertThat(flag2, not(nullValue()));
111+
112+
assertJsonEquals(flagJson(expectedFlag1, 1), flagJson(flag1));
113+
assertJsonEquals(flagJson(expectedFlag2, 1), flagJson(flag2));
114+
}
115+
116+
@Test
117+
public void addsFlag() throws Exception {
118+
TestDataV2 td = TestDataV2.synchronizer();
119+
Synchronizer sync = td.build(dataSourceBuildInputs());
120+
121+
FDv2SourceResult initResult = sync.next().get(5, TimeUnit.SECONDS);
122+
assertThat(initResult.getResultType(), equalTo(FDv2SourceResult.ResultType.CHANGE_SET));
123+
assertThat(initResult.getChangeSet().getType(), equalTo(ChangeSetType.Full));
124+
125+
td.update(td.flag("flag1").on(true));
126+
127+
FDv2SourceResult updateResult = sync.next().get(5, TimeUnit.SECONDS);
128+
assertThat(updateResult.getResultType(), equalTo(FDv2SourceResult.ResultType.CHANGE_SET));
129+
ChangeSet<ItemDescriptor> changeSet = updateResult.getChangeSet();
130+
assertThat(changeSet.getType(), equalTo(ChangeSetType.Partial));
131+
assertThat(changeSet.getData(), iterableWithSize(1));
132+
KeyedItems<ItemDescriptor> keyedItems = get(changeSet.getData(), 0).getValue();
133+
Map<String, ItemDescriptor> items = ImmutableMap.copyOf(keyedItems.getItems());
134+
assertThat(items.size(), equalTo(1));
135+
ItemDescriptor flag1 = items.get("flag1");
136+
assertThat(flag1, not(nullValue()));
137+
138+
ModelBuilders.FlagBuilder expectedFlag = flagBuilder("flag1").version(1).salt("")
139+
.on(true).offVariation(1).fallthroughVariation(0).variations(true, false);
140+
assertJsonEquals(flagJson(expectedFlag, 2), flagJson(flag1));
141+
}
142+
143+
@Test
144+
public void updatesFlag() throws Exception {
145+
TestDataV2 td = TestDataV2.synchronizer();
146+
td.update(td.flag("flag1")
147+
.on(false)
148+
.variationForUser("a", true)
149+
.ifMatch("name", LDValue.of("Lucy")).thenReturn(true));
150+
151+
Synchronizer sync = td.build(dataSourceBuildInputs());
152+
FDv2SourceResult initResult = sync.next().get(5, TimeUnit.SECONDS);
153+
assertThat(initResult.getResultType(), equalTo(FDv2SourceResult.ResultType.CHANGE_SET));
154+
155+
td.update(td.flag("flag1").on(true));
156+
157+
FDv2SourceResult updateResult = sync.next().get(5, TimeUnit.SECONDS);
158+
ChangeSet<ItemDescriptor> changeSet = updateResult.getChangeSet();
159+
Map<String, ItemDescriptor> items = ImmutableMap.copyOf(get(changeSet.getData(), 0).getValue().getItems());
160+
ItemDescriptor flag1 = items.get("flag1");
161+
162+
ModelBuilders.FlagBuilder expectedFlag = flagBuilder("flag1").version(2).salt("")
163+
.on(true).offVariation(1).fallthroughVariation(0).variations(true, false)
164+
.addTarget(0, "a").addContextTarget(ContextKind.DEFAULT, 0)
165+
.addRule("rule0", 0, "{\"contextKind\":\"user\",\"attribute\":\"name\",\"op\":\"in\",\"values\":[\"Lucy\"]}");
166+
assertJsonEquals(flagJson(expectedFlag, 2), flagJson(flag1));
167+
}
168+
169+
@Test
170+
public void deletesFlag() throws Exception {
171+
TestDataV2 td = TestDataV2.synchronizer();
172+
Synchronizer sync = td.build(dataSourceBuildInputs());
173+
174+
sync.next().get(5, TimeUnit.SECONDS);
175+
176+
td.update(td.flag("foo").on(false).valueForAll(LDValue.of("bar")));
177+
FDv2SourceResult addResult = sync.next().get(5, TimeUnit.SECONDS);
178+
assertThat(addResult.getChangeSet().getType(), equalTo(ChangeSetType.Partial));
179+
Map<String, ItemDescriptor> addItems = ImmutableMap.copyOf(get(addResult.getChangeSet().getData(), 0).getValue().getItems());
180+
assertThat(addItems.get("foo").getVersion(), equalTo(1));
181+
assertThat(addItems.get("foo").getItem(), notNullValue());
182+
183+
td.delete("foo");
184+
FDv2SourceResult deleteResult = sync.next().get(5, TimeUnit.SECONDS);
185+
assertThat(deleteResult.getChangeSet().getType(), equalTo(ChangeSetType.Partial));
186+
Map<String, ItemDescriptor> deleteItems = ImmutableMap.copyOf(get(deleteResult.getChangeSet().getData(), 0).getValue().getItems());
187+
assertThat(deleteItems.get("foo").getVersion(), equalTo(2));
188+
assertThat(deleteItems.get("foo").getItem(), nullValue());
189+
190+
sync.close();
191+
}
192+
193+
@Test
194+
public void flagConfigSimpleBoolean() throws Exception {
195+
Function<ModelBuilders.FlagBuilder, ModelBuilders.FlagBuilder> expectedBooleanFlag = fb ->
196+
fb.on(true).variations(true, false).offVariation(1).fallthroughVariation(0);
197+
198+
verifyFlag(f -> f, expectedBooleanFlag);
199+
verifyFlag(f -> f.booleanFlag(), expectedBooleanFlag);
200+
verifyFlag(f -> f.on(true), expectedBooleanFlag);
201+
verifyFlag(f -> f.on(false), fb -> expectedBooleanFlag.apply(fb).on(false));
202+
verifyFlag(f -> f.variationForAll(false), fb -> expectedBooleanFlag.apply(fb).fallthroughVariation(1));
203+
verifyFlag(f -> f.variationForAll(true), expectedBooleanFlag);
204+
}
205+
206+
@Test
207+
public void flagConfigStringVariations() throws Exception {
208+
verifyFlag(
209+
f -> f.variations(THREE_STRING_VALUES).offVariation(0).fallthroughVariation(2),
210+
fb -> fb.variations("red", "green", "blue").on(true).offVariation(0).fallthroughVariation(2)
211+
);
212+
}
213+
214+
@Test
215+
public void userTargets() throws Exception {
216+
Function<ModelBuilders.FlagBuilder, ModelBuilders.FlagBuilder> expectedBooleanFlag = fb ->
217+
fb.variations(true, false).on(true).offVariation(1).fallthroughVariation(0);
218+
219+
verifyFlag(
220+
f -> f.variationForUser("a", true).variationForUser("b", true),
221+
fb -> expectedBooleanFlag.apply(fb).addTarget(0, "a", "b")
222+
.addContextTarget(ContextKind.DEFAULT, 0)
223+
);
224+
}
225+
226+
@Test
227+
public void flagRules() throws Exception {
228+
Function<ModelBuilders.FlagBuilder, ModelBuilders.FlagBuilder> expectedBooleanFlag = fb ->
229+
fb.variations(true, false).on(true).offVariation(1).fallthroughVariation(0);
230+
231+
verifyFlag(
232+
f -> f.ifMatch("name", LDValue.of("Lucy")).thenReturn(true),
233+
fb -> expectedBooleanFlag.apply(fb).addRule("rule0", 0,
234+
"{\"contextKind\":\"user\",\"attribute\":\"name\",\"op\":\"in\",\"values\":[\"Lucy\"]}")
235+
);
236+
}
237+
238+
private void verifyFlag(
239+
Function<TestData.FlagBuilder, TestData.FlagBuilder> configureFlag,
240+
Function<ModelBuilders.FlagBuilder, ModelBuilders.FlagBuilder> configureExpectedFlag
241+
) throws Exception {
242+
ModelBuilders.FlagBuilder expectedFlag = flagBuilder("flagkey").version(1).salt("");
243+
expectedFlag = configureExpectedFlag.apply(expectedFlag);
244+
245+
TestDataV2 td = TestDataV2.synchronizer();
246+
Synchronizer sync = td.build(dataSourceBuildInputs());
247+
sync.next().get(5, TimeUnit.SECONDS);
248+
249+
td.update(configureFlag.apply(td.flag("flagkey")));
250+
251+
FDv2SourceResult result = sync.next().get(5, TimeUnit.SECONDS);
252+
assertThat(result.getResultType(), equalTo(FDv2SourceResult.ResultType.CHANGE_SET));
253+
ChangeSet<ItemDescriptor> changeSet = result.getChangeSet();
254+
Map<String, ItemDescriptor> items = ImmutableMap.copyOf(get(changeSet.getData(), 0).getValue().getItems());
255+
ItemDescriptor flag = items.get("flagkey");
256+
assertJsonEquals(flagJson(expectedFlag, 1), flagJson(flag));
257+
}
258+
259+
private static String flagJson(ModelBuilders.FlagBuilder flagBuilder, int version) {
260+
return DataModel.FEATURES.serialize(new ItemDescriptor(version, flagBuilder.build()));
261+
}
262+
263+
private static String flagJson(ItemDescriptor flag) {
264+
return DataModel.FEATURES.serialize(flag);
265+
}
266+
267+
private static class CapturingDataSourceUpdates implements com.launchdarkly.sdk.server.subsystems.DataSourceUpdateSink,
268+
com.launchdarkly.sdk.server.subsystems.DataSourceUpdateSinkV2 {
269+
BlockingQueue<com.launchdarkly.sdk.server.subsystems.DataStoreTypes.FullDataSet<ItemDescriptor>> inits =
270+
new LinkedBlockingQueue<>();
271+
BlockingQueue<UpsertParams> upserts = new LinkedBlockingQueue<>();
272+
BlockingQueue<ChangeSet<ItemDescriptor>> applies = new LinkedBlockingQueue<>();
273+
boolean valid;
274+
275+
@Override
276+
public boolean init(com.launchdarkly.sdk.server.subsystems.DataStoreTypes.FullDataSet<ItemDescriptor> allData) {
277+
inits.add(allData);
278+
return true;
279+
}
280+
281+
@Override
282+
public boolean upsert(DataKind kind, String key, ItemDescriptor item) {
283+
upserts.add(new UpsertParams(kind, key, item));
284+
return true;
285+
}
286+
287+
@Override
288+
public DataStoreStatusProvider getDataStoreStatusProvider() {
289+
return null;
290+
}
291+
292+
@Override
293+
public void updateStatus(State newState, ErrorInfo newError) {
294+
valid = newState == State.VALID;
295+
}
296+
297+
@Override
298+
public boolean apply(ChangeSet<ItemDescriptor> changeSet) {
299+
applies.add(changeSet);
300+
return true;
301+
}
302+
}
303+
304+
private static class UpsertParams {
305+
final DataKind kind;
306+
final String key;
307+
final ItemDescriptor item;
308+
309+
UpsertParams(DataKind kind, String key, ItemDescriptor item) {
310+
this.kind = kind;
311+
this.key = key;
312+
this.item = item;
313+
}
314+
}
315+
}

0 commit comments

Comments
 (0)