Skip to content

Commit aba46ef

Browse files
committed
Update comment
1 parent da27015 commit aba46ef

File tree

3 files changed

+825
-1
lines changed

3 files changed

+825
-1
lines changed

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/datasources/FDv2SourceResult.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
public class FDv2SourceResult {
1313
public enum State {
1414
/**
15-
* The data source has encountered an interruption and will attempt to reconnect.
15+
* The data source has encountered an interruption and will attempt to reconnect. This isn't intended to be used
16+
* with an initializer, and instead TERMINAL_ERROR should be used. When this status is used with an initializer
17+
* it will still be a terminal state.
1618
*/
1719
INTERRUPTED,
1820
/**
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
package com.launchdarkly.sdk.server;
2+
3+
import com.launchdarkly.sdk.internal.fdv2.sources.FDv2ChangeSet;
4+
import com.launchdarkly.sdk.internal.fdv2.sources.Selector;
5+
import com.launchdarkly.sdk.internal.http.HttpErrors;
6+
import com.launchdarkly.sdk.server.datasources.FDv2SourceResult;
7+
import com.launchdarkly.sdk.server.datasources.SelectorSource;
8+
import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider;
9+
import com.launchdarkly.sdk.json.SerializationException;
10+
11+
import org.junit.Test;
12+
13+
import java.io.IOException;
14+
import java.util.concurrent.CompletableFuture;
15+
import java.util.concurrent.ExecutionException;
16+
import java.util.concurrent.TimeUnit;
17+
18+
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertNotNull;
20+
import static org.junit.Assert.assertNull;
21+
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.times;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.when;
26+
27+
@SuppressWarnings("javadoc")
28+
public class PollingInitializerImplTest extends BaseTest {
29+
30+
private FDv2Requestor mockRequestor() {
31+
return mock(FDv2Requestor.class);
32+
}
33+
34+
private SelectorSource mockSelectorSource() {
35+
SelectorSource source = mock(SelectorSource.class);
36+
when(source.getSelector()).thenReturn(Selector.EMPTY);
37+
return source;
38+
}
39+
40+
private FDv2Requestor.FDv2PollingResponse makeSuccessResponse() {
41+
String json = "{\n" +
42+
" \"events\": [\n" +
43+
" {\n" +
44+
" \"event\": \"server-intent\",\n" +
45+
" \"data\": {\n" +
46+
" \"payloads\": [{\n" +
47+
" \"id\": \"payload-1\",\n" +
48+
" \"target\": 100,\n" +
49+
" \"intentCode\": \"xfer-full\",\n" +
50+
" \"reason\": \"payload-missing\"\n" +
51+
" }]\n" +
52+
" }\n" +
53+
" },\n" +
54+
" {\n" +
55+
" \"event\": \"payload-transferred\",\n" +
56+
" \"data\": {\n" +
57+
" \"state\": \"(p:payload-1:100)\",\n" +
58+
" \"version\": 100\n" +
59+
" }\n" +
60+
" }\n" +
61+
" ]\n" +
62+
"}";
63+
64+
try {
65+
return new FDv2Requestor.FDv2PollingResponse(
66+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(json),
67+
okhttp3.Headers.of()
68+
);
69+
} catch (Exception e) {
70+
throw new RuntimeException(e);
71+
}
72+
}
73+
74+
@Test
75+
public void successfulInitialization() throws Exception {
76+
FDv2Requestor requestor = mockRequestor();
77+
SelectorSource selectorSource = mockSelectorSource();
78+
79+
FDv2Requestor.FDv2PollingResponse response = makeSuccessResponse();
80+
when(requestor.Poll(any(Selector.class)))
81+
.thenReturn(CompletableFuture.completedFuture(response));
82+
83+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
84+
85+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
86+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
87+
88+
assertNotNull(result);
89+
assertEquals(FDv2SourceResult.ResultType.CHANGE_SET, result.getResultType());
90+
assertNotNull(result.getChangeSet());
91+
92+
verify(requestor, times(1)).Poll(any(Selector.class));
93+
verify(requestor, times(1)).close();
94+
}
95+
96+
@Test
97+
public void httpRecoverableError() throws Exception {
98+
FDv2Requestor requestor = mockRequestor();
99+
SelectorSource selectorSource = mockSelectorSource();
100+
101+
when(requestor.Poll(any(Selector.class)))
102+
.thenReturn(CompletableFuture.failedFuture(new HttpErrors.HttpErrorException(503)));
103+
104+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
105+
106+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
107+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
108+
109+
assertNotNull(result);
110+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
111+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
112+
assertNotNull(result.getStatus().getErrorInfo());
113+
assertEquals(DataSourceStatusProvider.ErrorKind.ERROR_RESPONSE, result.getStatus().getErrorInfo().getKind());
114+
115+
verify(requestor, times(1)).close();
116+
}
117+
118+
@Test
119+
public void httpNonRecoverableError() throws Exception {
120+
FDv2Requestor requestor = mockRequestor();
121+
SelectorSource selectorSource = mockSelectorSource();
122+
123+
when(requestor.Poll(any(Selector.class)))
124+
.thenReturn(CompletableFuture.failedFuture(new HttpErrors.HttpErrorException(401)));
125+
126+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
127+
128+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
129+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
130+
131+
assertNotNull(result);
132+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
133+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
134+
assertEquals(DataSourceStatusProvider.ErrorKind.ERROR_RESPONSE, result.getStatus().getErrorInfo().getKind());
135+
136+
verify(requestor, times(1)).close();
137+
}
138+
139+
@Test
140+
public void networkError() throws Exception {
141+
FDv2Requestor requestor = mockRequestor();
142+
SelectorSource selectorSource = mockSelectorSource();
143+
144+
when(requestor.Poll(any(Selector.class)))
145+
.thenReturn(CompletableFuture.failedFuture(new IOException("Connection refused")));
146+
147+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
148+
149+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
150+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
151+
152+
assertNotNull(result);
153+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
154+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
155+
assertEquals(DataSourceStatusProvider.ErrorKind.NETWORK_ERROR, result.getStatus().getErrorInfo().getKind());
156+
157+
verify(requestor, times(1)).close();
158+
}
159+
160+
@Test
161+
public void serializationError() throws Exception {
162+
FDv2Requestor requestor = mockRequestor();
163+
SelectorSource selectorSource = mockSelectorSource();
164+
165+
when(requestor.Poll(any(Selector.class)))
166+
.thenReturn(CompletableFuture.failedFuture(new SerializationException("Invalid JSON")));
167+
168+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
169+
170+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
171+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
172+
173+
assertNotNull(result);
174+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
175+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
176+
assertEquals(DataSourceStatusProvider.ErrorKind.INVALID_DATA, result.getStatus().getErrorInfo().getKind());
177+
178+
verify(requestor, times(1)).close();
179+
}
180+
181+
@Test
182+
public void shutdownBeforePollCompletes() throws Exception {
183+
FDv2Requestor requestor = mockRequestor();
184+
SelectorSource selectorSource = mockSelectorSource();
185+
186+
CompletableFuture<FDv2Requestor.FDv2PollingResponse> delayedResponse = new CompletableFuture<>();
187+
when(requestor.Poll(any(Selector.class))).thenReturn(delayedResponse);
188+
189+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
190+
191+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
192+
193+
// Shutdown before poll completes
194+
Thread.sleep(100);
195+
initializer.shutdown();
196+
197+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
198+
199+
assertNotNull(result);
200+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
201+
assertEquals(FDv2SourceResult.State.SHUTDOWN, result.getStatus().getState());
202+
assertNull(result.getStatus().getErrorInfo());
203+
204+
verify(requestor, times(1)).close();
205+
}
206+
207+
@Test
208+
public void shutdownAfterPollCompletes() throws Exception {
209+
FDv2Requestor requestor = mockRequestor();
210+
SelectorSource selectorSource = mockSelectorSource();
211+
212+
FDv2Requestor.FDv2PollingResponse response = makeSuccessResponse();
213+
when(requestor.Poll(any(Selector.class)))
214+
.thenReturn(CompletableFuture.completedFuture(response));
215+
216+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
217+
218+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
219+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
220+
221+
assertNotNull(result);
222+
assertEquals(FDv2SourceResult.ResultType.CHANGE_SET, result.getResultType());
223+
224+
// Shutdown after completion should still work
225+
initializer.shutdown();
226+
227+
verify(requestor, times(1)).close();
228+
}
229+
230+
@Test
231+
public void errorEventInResponse() throws Exception {
232+
FDv2Requestor requestor = mockRequestor();
233+
SelectorSource selectorSource = mockSelectorSource();
234+
235+
String errorJson = "{\n" +
236+
" \"events\": [\n" +
237+
" {\n" +
238+
" \"event\": \"error\",\n" +
239+
" \"data\": {\n" +
240+
" \"error\": \"invalid-request\",\n" +
241+
" \"reason\": \"bad request\"\n" +
242+
" }\n" +
243+
" }\n" +
244+
" ]\n" +
245+
"}";
246+
247+
FDv2Requestor.FDv2PollingResponse response = new FDv2Requestor.FDv2PollingResponse(
248+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(errorJson),
249+
okhttp3.Headers.of()
250+
);
251+
252+
when(requestor.Poll(any(Selector.class)))
253+
.thenReturn(CompletableFuture.completedFuture(response));
254+
255+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
256+
257+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
258+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
259+
260+
assertNotNull(result);
261+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
262+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
263+
264+
verify(requestor, times(1)).close();
265+
}
266+
267+
@Test
268+
public void goodbyeEventInResponse() throws Exception {
269+
FDv2Requestor requestor = mockRequestor();
270+
SelectorSource selectorSource = mockSelectorSource();
271+
272+
String goodbyeJson = "{\n" +
273+
" \"events\": [\n" +
274+
" {\n" +
275+
" \"event\": \"goodbye\",\n" +
276+
" \"data\": {\n" +
277+
" \"reason\": \"service-unavailable\"\n" +
278+
" }\n" +
279+
" }\n" +
280+
" ]\n" +
281+
"}";
282+
283+
FDv2Requestor.FDv2PollingResponse response = new FDv2Requestor.FDv2PollingResponse(
284+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(goodbyeJson),
285+
okhttp3.Headers.of()
286+
);
287+
288+
when(requestor.Poll(any(Selector.class)))
289+
.thenReturn(CompletableFuture.completedFuture(response));
290+
291+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
292+
293+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
294+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
295+
296+
assertNotNull(result);
297+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
298+
assertEquals(FDv2SourceResult.State.GOODBYE, result.getStatus().getState());
299+
300+
verify(requestor, times(1)).close();
301+
}
302+
303+
@Test
304+
public void emptyEventsArray() throws Exception {
305+
FDv2Requestor requestor = mockRequestor();
306+
SelectorSource selectorSource = mockSelectorSource();
307+
308+
String emptyJson = "{\"events\": []}";
309+
310+
FDv2Requestor.FDv2PollingResponse response = new FDv2Requestor.FDv2PollingResponse(
311+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(emptyJson),
312+
okhttp3.Headers.of()
313+
);
314+
315+
when(requestor.Poll(any(Selector.class)))
316+
.thenReturn(CompletableFuture.completedFuture(response));
317+
318+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
319+
320+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
321+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
322+
323+
// Empty events array should result in terminal error
324+
assertNotNull(result);
325+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
326+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
327+
328+
verify(requestor, times(1)).close();
329+
}
330+
}

0 commit comments

Comments
 (0)