Skip to content

Commit dd3942a

Browse files
committed
feat: Add the flag-overrides capability to the contract test service
The contract test service advertises the flag-overrides capability and accepts the overrides object in the SDK configuration: filePaths, duplicateKeysHandling, changeDetection, and pollIntervalMs. The block configures FileOverrides.source() on the data system builder. An overrides block without a dataSystem block is rejected, because the override source is an option of the FDv2 data system. The service reads its listening port from the PORT environment variable when set, which the Makefile already passes, so several test services can run side by side. The default stays 8000.
1 parent 657a598 commit dd3942a

3 files changed

Lines changed: 70 additions & 4 deletions

File tree

‎lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ public static class SdkConfigParams {
3434
SdkConfigServiceEndpointParams serviceEndpoints;
3535
SdkConfigHookParams hooks;
3636
SdkConfigDataSystemParams dataSystem;
37+
SdkConfigOverridesParams overrides;
38+
}
39+
40+
/**
41+
* Configuration for the SDK's file-based flag overrides (the "flag-overrides" capability).
42+
*/
43+
public static class SdkConfigOverridesParams {
44+
/** Absolute paths of the override files, in precedence order. */
45+
String[] filePaths;
46+
/** "fail" (the default) or "ignore". */
47+
String duplicateKeysHandling;
48+
/** "polling" or "watching"; null means the SDK default. */
49+
String changeDetection;
50+
/** The polling interval in milliseconds; the SDK may raise it to its minimum. */
51+
Long pollIntervalMs;
3752
}
3853

3954
public static class SdkConfigStreamParams {

‎lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
import com.launchdarkly.sdk.server.integrations.FDv2PollingInitializerBuilder;
3333
import com.launchdarkly.sdk.server.integrations.FDv2PollingSynchronizerBuilder;
3434
import com.launchdarkly.sdk.server.integrations.FDv2StreamingSynchronizerBuilder;
35+
import com.launchdarkly.sdk.server.integrations.FileData;
36+
import com.launchdarkly.sdk.server.integrations.FileOverrideSourceBuilder;
37+
import com.launchdarkly.sdk.server.integrations.FileOverrides;
3538
import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider;
3639
import com.launchdarkly.sdk.server.subsystems.DataSourceBuilder;
3740
import com.launchdarkly.sdk.server.datasources.Initializer;
@@ -68,6 +71,7 @@
6871
import sdktest.Representations.SdkConfigParams;
6972
import sdktest.Representations.SdkConfigDataSystemParams;
7073
import sdktest.Representations.SdkConfigDataInitializerParams;
74+
import sdktest.Representations.SdkConfigOverridesParams;
7175
import sdktest.Representations.SdkConfigSynchronizerParams;
7276
import sdktest.Representations.SdkConfigPollingParams;
7377
import sdktest.Representations.SdkConfigStreamingParams;
@@ -587,12 +591,53 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) {
587591
dataSystemBuilder.fDv1FallbackSynchronizer(fdv1Polling);
588592
}
589593

594+
if (params.overrides != null) {
595+
dataSystemBuilder.overrides(makeOverridesConfig(params.overrides));
596+
}
597+
590598
builder.dataSystem(dataSystemBuilder);
599+
} else if (params.overrides != null) {
600+
throw new IllegalArgumentException("flag overrides require the data system to be configured");
591601
}
592602

593603
return builder.build();
594604
}
595605

606+
private static FileOverrideSourceBuilder makeOverridesConfig(SdkConfigOverridesParams params) {
607+
FileOverrideSourceBuilder overrides = FileOverrides.source();
608+
if (params.filePaths != null) {
609+
overrides.filePaths(params.filePaths);
610+
}
611+
if (params.duplicateKeysHandling != null) {
612+
switch (params.duplicateKeysHandling) {
613+
case "fail":
614+
overrides.duplicateKeysHandling(FileData.DuplicateKeysHandling.FAIL);
615+
break;
616+
case "ignore":
617+
overrides.duplicateKeysHandling(FileData.DuplicateKeysHandling.IGNORE);
618+
break;
619+
default:
620+
throw new IllegalArgumentException("unknown duplicate keys handling: " + params.duplicateKeysHandling);
621+
}
622+
}
623+
if (params.changeDetection != null) {
624+
switch (params.changeDetection) {
625+
case "polling":
626+
overrides.changeDetection(FileOverrides.ChangeDetection.POLLING);
627+
break;
628+
case "watching":
629+
overrides.changeDetection(FileOverrides.ChangeDetection.WATCHING);
630+
break;
631+
default:
632+
throw new IllegalArgumentException("unknown change detection mode: " + params.changeDetection);
633+
}
634+
}
635+
if (params.pollIntervalMs != null) {
636+
overrides.pollInterval(Duration.ofMillis(params.pollIntervalMs));
637+
}
638+
return overrides;
639+
}
640+
596641
private DataSourceBuilder<Synchronizer> createSynchronizer(
597642
SdkConfigSynchronizerParams synchronizer,
598643
String payloadFilter) {

‎lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import sdktest.Representations.Status;
2222

2323
public class TestService {
24-
private static final int PORT = 8000;
24+
private static final int DEFAULT_PORT = 8000;
2525
private static final String[] CAPABILITIES = new String[]{
2626
"all-flags-client-side-only",
2727
"all-flags-details-only-for-tracked-flags",
@@ -47,7 +47,8 @@ public class TestService {
4747
"fdv1-fallback",
4848
"instance-id",
4949
"retry-conformance-fdv1-streaming",
50-
"retry-conformance-fdv1-polling"
50+
"retry-conformance-fdv1-polling",
51+
"flag-overrides"
5152
};
5253

5354
static final Gson gson = new GsonBuilder().serializeNulls().create();
@@ -83,10 +84,15 @@ public static void main(String[] args) throws Exception {
8384
.addRegex("POST", Pattern.compile("/clients/(.*)"), ctx -> service.postClientCommand(diableKeepAlive(ctx)))
8485
.addRegex("DELETE", Pattern.compile("/clients/(.*)"), ctx -> service.deleteClient(diableKeepAlive(ctx)));
8586

86-
HttpServer server = HttpServer.start(PORT, router);
87+
int port = DEFAULT_PORT;
88+
String portEnv = System.getenv("PORT");
89+
if (portEnv != null && !portEnv.isEmpty()) {
90+
port = Integer.parseInt(portEnv);
91+
}
92+
HttpServer server = HttpServer.start(port, router);
8793
server.getRecorder().setEnabled(false); // don't accumulate a request log
8894

89-
System.out.println("Listening on port " + PORT);
95+
System.out.println("Listening on port " + port);
9096

9197
// need to explicitly sleep because HttpServer now starts as a daemon thread
9298
while (true) {

0 commit comments

Comments
 (0)