Skip to content

Commit 7c84a68

Browse files
committed
Extend polling tests for INTERNAL_ERROR
1 parent 97ac7c4 commit 7c84a68

File tree

2 files changed

+237
-1
lines changed

2 files changed

+237
-1
lines changed

lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/PollingInitializerImplTest.java

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,98 @@ public void emptyEventsArray() throws Exception {
329329
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
330330
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
331331

332-
332+
333+
}
334+
335+
@Test
336+
public void internalErrorWithInvalidDataKind() throws Exception {
337+
FDv2Requestor requestor = mockRequestor();
338+
SelectorSource selectorSource = mockSelectorSource();
339+
340+
// Create a response with malformed payload-transferred event. `state->states`.
341+
// This will trigger JSON_ERROR internal error which maps to INVALID_DATA
342+
String malformedPutObjectJson = "{\n" +
343+
" \"events\": [\n" +
344+
" {\n" +
345+
" \"event\": \"server-intent\",\n" +
346+
" \"data\": {\n" +
347+
" \"payloads\": [{\n" +
348+
" \"id\": \"payload-1\",\n" +
349+
" \"target\": 100,\n" +
350+
" \"intentCode\": \"xfer-full\",\n" +
351+
" \"reason\": \"payload-missing\"\n" +
352+
" }]\n" +
353+
" }\n" +
354+
" },\n" +
355+
" {\n" +
356+
" \"event\": \"payload-transferred\",\n" +
357+
" \"data\": {\n" +
358+
" \"states\": \"(p:payload-1:100)\",\n" +
359+
" \"version\": 100\n" +
360+
" }\n" +
361+
" },\n" +
362+
" {\n" +
363+
" \"event\": \"put-object\",\n" +
364+
" \"data\": {}\n" +
365+
" }\n" +
366+
" ]\n" +
367+
"}";
368+
369+
FDv2Requestor.FDv2PayloadResponse response = new FDv2Requestor.FDv2PayloadResponse(
370+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(malformedPutObjectJson),
371+
okhttp3.Headers.of()
372+
);
373+
374+
when(requestor.Poll(any(Selector.class)))
375+
.thenReturn(CompletableFuture.completedFuture(response));
376+
377+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
378+
379+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
380+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
381+
382+
assertNotNull(result);
383+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
384+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
385+
assertEquals(DataSourceStatusProvider.ErrorKind.INVALID_DATA, result.getStatus().getErrorInfo().getKind());
386+
387+
388+
}
389+
390+
@Test
391+
public void internalErrorWithUnknownKind() throws Exception {
392+
FDv2Requestor requestor = mockRequestor();
393+
SelectorSource selectorSource = mockSelectorSource();
394+
395+
// Create a response with an unrecognized event type
396+
// This will trigger UNKNOWN_EVENT internal error which maps to UNKNOWN error kind
397+
String unknownEventJson = "{\n" +
398+
" \"events\": [\n" +
399+
" {\n" +
400+
" \"event\": \"unrecognized-event-type\",\n" +
401+
" \"data\": {}\n" +
402+
" }\n" +
403+
" ]\n" +
404+
"}";
405+
406+
FDv2Requestor.FDv2PayloadResponse response = new FDv2Requestor.FDv2PayloadResponse(
407+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(unknownEventJson),
408+
okhttp3.Headers.of()
409+
);
410+
411+
when(requestor.Poll(any(Selector.class)))
412+
.thenReturn(CompletableFuture.completedFuture(response));
413+
414+
PollingInitializerImpl initializer = new PollingInitializerImpl(requestor, testLogger, selectorSource);
415+
416+
CompletableFuture<FDv2SourceResult> resultFuture = initializer.run();
417+
FDv2SourceResult result = resultFuture.get(5, TimeUnit.SECONDS);
418+
419+
assertNotNull(result);
420+
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
421+
assertEquals(FDv2SourceResult.State.TERMINAL_ERROR, result.getStatus().getState());
422+
assertEquals(DataSourceStatusProvider.ErrorKind.UNKNOWN, result.getStatus().getErrorInfo().getKind());
423+
424+
333425
}
334426
}

lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/PollingSynchronizerImplTest.java

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,4 +722,148 @@ public void nonRecoverableThenRecoverableErrorStopsPolling() throws Exception {
722722
executor.shutdown();
723723
}
724724
}
725+
726+
@Test
727+
public void internalErrorWithInvalidDataKindContinuesPolling() throws Exception {
728+
FDv2Requestor requestor = mockRequestor();
729+
SelectorSource selectorSource = mockSelectorSource();
730+
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
731+
732+
AtomicInteger callCount = new AtomicInteger(0);
733+
when(requestor.Poll(any(Selector.class))).thenAnswer(invocation -> {
734+
int count = callCount.incrementAndGet();
735+
if (count == 1) {
736+
// First call returns response with malformed put-object which triggers INTERNAL_ERROR (INVALID_DATA)
737+
String malformedPutObjectJson = "{\n" +
738+
" \"events\": [\n" +
739+
" {\n" +
740+
" \"event\": \"server-intent\",\n" +
741+
" \"data\": {\n" +
742+
" \"payloads\": [{\n" +
743+
" \"id\": \"payload-1\",\n" +
744+
" \"target\": 100,\n" +
745+
" \"intentCode\": \"xfer-full\",\n" +
746+
" \"reason\": \"payload-missing\"\n" +
747+
" }]\n" +
748+
" }\n" +
749+
" },\n" +
750+
" {\n" +
751+
" \"event\": \"payload-transferred\",\n" +
752+
" \"data\": {\n" +
753+
" \"state\": \"(p:payload-1:100)\",\n" +
754+
" \"version\": 100\n" +
755+
" }\n" +
756+
" },\n" +
757+
" {\n" +
758+
" \"event\": \"put-object\",\n" +
759+
" \"data\": {}\n" +
760+
" }\n" +
761+
" ]\n" +
762+
"}";
763+
764+
return CompletableFuture.completedFuture(new FDv2Requestor.FDv2PayloadResponse(
765+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(malformedPutObjectJson),
766+
okhttp3.Headers.of()
767+
));
768+
} else {
769+
// Subsequent calls succeed
770+
return CompletableFuture.completedFuture(makeSuccessResponse());
771+
}
772+
});
773+
774+
try {
775+
PollingSynchronizerImpl synchronizer = new PollingSynchronizerImpl(
776+
requestor,
777+
testLogger,
778+
selectorSource,
779+
executor,
780+
Duration.ofMillis(50)
781+
);
782+
783+
// Wait for multiple polls
784+
Thread.sleep(250);
785+
786+
// First result should be interrupted with INVALID_DATA error kind
787+
FDv2SourceResult result1 = synchronizer.next().get(1, TimeUnit.SECONDS);
788+
assertNotNull(result1);
789+
assertEquals(FDv2SourceResult.ResultType.STATUS, result1.getResultType());
790+
assertEquals(FDv2SourceResult.State.INTERRUPTED, result1.getStatus().getState());
791+
assertEquals(DataSourceStatusProvider.ErrorKind.INVALID_DATA, result1.getStatus().getErrorInfo().getKind());
792+
793+
// Second result should be success (polling continued)
794+
FDv2SourceResult result2 = synchronizer.next().get(1, TimeUnit.SECONDS);
795+
assertNotNull(result2);
796+
assertEquals(FDv2SourceResult.ResultType.CHANGE_SET, result2.getResultType());
797+
798+
// Verify polling continued after internal error
799+
assertTrue("Should have made at least 2 calls", callCount.get() >= 2);
800+
801+
synchronizer.close();
802+
} finally {
803+
executor.shutdown();
804+
}
805+
}
806+
807+
@Test
808+
public void internalErrorWithUnknownKindContinuesPolling() throws Exception {
809+
FDv2Requestor requestor = mockRequestor();
810+
SelectorSource selectorSource = mockSelectorSource();
811+
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
812+
813+
AtomicInteger callCount = new AtomicInteger(0);
814+
when(requestor.Poll(any(Selector.class))).thenAnswer(invocation -> {
815+
int count = callCount.incrementAndGet();
816+
if (count == 1) {
817+
// First call returns response with unknown event which triggers INTERNAL_ERROR (UNKNOWN)
818+
String unknownEventJson = "{\n" +
819+
" \"events\": [\n" +
820+
" {\n" +
821+
" \"event\": \"unrecognized-event-type\",\n" +
822+
" \"data\": {}\n" +
823+
" }\n" +
824+
" ]\n" +
825+
"}";
826+
827+
return CompletableFuture.completedFuture(new FDv2Requestor.FDv2PayloadResponse(
828+
com.launchdarkly.sdk.internal.fdv2.payloads.FDv2Event.parseEventsArray(unknownEventJson),
829+
okhttp3.Headers.of()
830+
));
831+
} else {
832+
// Subsequent calls succeed
833+
return CompletableFuture.completedFuture(makeSuccessResponse());
834+
}
835+
});
836+
837+
try {
838+
PollingSynchronizerImpl synchronizer = new PollingSynchronizerImpl(
839+
requestor,
840+
testLogger,
841+
selectorSource,
842+
executor,
843+
Duration.ofMillis(50)
844+
);
845+
846+
// Wait for multiple polls
847+
Thread.sleep(250);
848+
849+
// First result should be interrupted with UNKNOWN error kind
850+
FDv2SourceResult result1 = synchronizer.next().get(1, TimeUnit.SECONDS);
851+
assertNotNull(result1);
852+
assertEquals(FDv2SourceResult.ResultType.STATUS, result1.getResultType());
853+
assertEquals(FDv2SourceResult.State.INTERRUPTED, result1.getStatus().getState());
854+
assertEquals(DataSourceStatusProvider.ErrorKind.UNKNOWN, result1.getStatus().getErrorInfo().getKind());
855+
856+
// Second result should be success (polling continued)
857+
FDv2SourceResult result2 = synchronizer.next().get(1, TimeUnit.SECONDS);
858+
assertNotNull(result2);
859+
assertEquals(FDv2SourceResult.ResultType.CHANGE_SET, result2.getResultType());
860+
861+
// Verify polling continued after internal error
862+
assertTrue("Should have made at least 2 calls", callCount.get() >= 2);
863+
864+
synchronizer.close();
865+
} finally {
866+
executor.shutdown();
867+
}
868+
}
725869
}

0 commit comments

Comments
 (0)