From 756081e15ec1d35b96e0c8a12e472c995fbafa5c Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 3 Nov 2026 18:43:25 +0000 Subject: [PATCH] feat: Configure or disable the FDv1 fallback per connection mode ConnectionModeBuilder gains fdv1FallbackSynchronizer(), which takes an FDv1FallbackSynchronizerEntry from DataSystemComponents.fdv1FallbackSynchronizer() with a poll interval and a service endpoints override, and disableFdv1Fallback(), which leaves the mode with no FDv1 fallback. A mode that does neither keeps the SDK's default FDv1 fallback for that mode. The FDv1 fallback synchronizer polls the override's polling endpoint when one is given and the SDK's polling endpoint otherwise. Generated-by: termag --- .../sdk/android/DataSystemComponents.java | 32 ++++++- .../sdk/android/FDv2EntryConverter.java | 6 ++ .../integrations/ConnectionModeBuilder.java | 66 ++++++++++++++ .../integrations/DataSystemBuilder.java | 30 ++++--- .../FDv1FallbackSynchronizerEntry.java | 70 +++++++++++++++ .../sdk/android/DataSystemBuilderTest.java | 31 +++++++ .../sdk/android/DataSystemComponentsTest.java | 86 ++++++++++++++++++- .../ConnectionModeBuilderTest.java | 29 +++++++ 8 files changed, 335 insertions(+), 15 deletions(-) create mode 100644 launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/FDv1FallbackSynchronizerEntry.java diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DataSystemComponents.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DataSystemComponents.java index 7e6c5ec1d..a77a1b74c 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DataSystemComponents.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DataSystemComponents.java @@ -4,6 +4,7 @@ import com.launchdarkly.sdk.android.integrations.CacheInitializerEntry; import com.launchdarkly.sdk.android.integrations.ConnectionModeBuilder; import com.launchdarkly.sdk.android.integrations.DataSystemBuilder; +import com.launchdarkly.sdk.android.integrations.FDv1FallbackSynchronizerEntry; import com.launchdarkly.sdk.android.integrations.PollingInitializerEntry; import com.launchdarkly.sdk.android.integrations.PollingSynchronizerEntry; import com.launchdarkly.sdk.android.integrations.StreamingSynchronizerEntry; @@ -150,6 +151,14 @@ public Synchronizer build(DataSourceBuildInputs inputs) { static final class FDv1PollingSynchronizerBuilderImpl implements DataSourceBuilder { protected int pollIntervalMillis = LDConfig.DEFAULT_POLL_INTERVAL_MILLIS; + private ServiceEndpoints serviceEndpointsOverride; + + static FDv1PollingSynchronizerBuilderImpl fromEntry(FDv1FallbackSynchronizerEntry entry) { + FDv1PollingSynchronizerBuilderImpl b = new FDv1PollingSynchronizerBuilderImpl(); + b.pollIntervalMillis = entry.getPollIntervalMillis(); + b.serviceEndpointsOverride = entry.getServiceEndpointsOverride(); + return b; + } public FDv1PollingSynchronizerBuilderImpl pollIntervalMillis(int pollIntervalMillis) { this.pollIntervalMillis = pollIntervalMillis <= LDConfig.DEFAULT_POLL_INTERVAL_MILLIS ? @@ -162,8 +171,15 @@ public Synchronizer build(DataSourceBuildInputs inputs) { // The FDv1 endpoints have no POST variant. When usePost is enabled, the fetcher // sends the context in the request body with REPORT so it stays out of the // request URI. useReport has no effect inside the data system. + ServiceEndpoints endpoints = serviceEndpointsOverride != null + ? serviceEndpointsOverride + : inputs.getServiceEndpoints(); + URI pollingBase = StandardEndpoints.selectBaseUri( + endpoints.getPollingBaseUri(), + StandardEndpoints.DEFAULT_POLLING_BASE_URI, + "polling", inputs.getBaseLogger()); FeatureFetcher fetcher = new HttpFeatureFlagFetcher( - inputs.getServiceEndpoints().getPollingBaseUri(), + pollingBase, inputs.isEvaluationReasons(), inputs.getHttp().shouldUsePost(), LDUtil.makeHttpProperties(inputs.getHttp()), @@ -243,6 +259,20 @@ public static StreamingSynchronizerEntry streamingSynchronizer() { return new StreamingSynchronizerEntry(); } + /** + * Returns an entry for the FDv1 fallback synchronizer of a connection mode. + *

+ * The SDK switches to the FDv1 fallback synchronizer when LaunchDarkly directs it to + * fall back to FDv1. Pass the entry to + * {@link ConnectionModeBuilder#fdv1FallbackSynchronizer(FDv1FallbackSynchronizerEntry)} + * to set its poll interval or service endpoints for that mode. + * + * @return an FDv1 fallback synchronizer entry + */ + public static FDv1FallbackSynchronizerEntry fdv1FallbackSynchronizer() { + return new FDv1FallbackSynchronizerEntry(); + } + /** * Produces the default mode table used by {@link DataSystemBuilder#buildModeTable}. * Defined here (rather than in {@code DataSystemBuilder}) because the FDv1 fallback diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/FDv2EntryConverter.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/FDv2EntryConverter.java index 07a72e7b8..30fd307b9 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/FDv2EntryConverter.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/FDv2EntryConverter.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.android; import com.launchdarkly.sdk.android.integrations.CacheInitializerEntry; +import com.launchdarkly.sdk.android.integrations.FDv1FallbackSynchronizerEntry; import com.launchdarkly.sdk.android.integrations.InitializerEntry; import com.launchdarkly.sdk.android.integrations.PollingInitializerEntry; import com.launchdarkly.sdk.android.integrations.PollingSynchronizerEntry; @@ -46,6 +47,11 @@ public static DataSourceBuilder toSynchronizerBuilder(Synchronizer throw new IllegalArgumentException("Unsupported SynchronizerEntry: " + entry.getClass().getName()); } + public static DataSourceBuilder toFdv1FallbackSynchronizerBuilder( + FDv1FallbackSynchronizerEntry entry) { + return DataSystemComponents.FDv1PollingSynchronizerBuilderImpl.fromEntry(entry); + } + public static List> toInitializerBuilders(List entries) { List> out = new ArrayList<>(entries.size()); for (InitializerEntry entry : entries) { diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilder.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilder.java index cc9755cb0..d92b2b9bf 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilder.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilder.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.android.integrations; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.launchdarkly.sdk.android.ConnectionMode; import com.launchdarkly.sdk.android.DataSystemComponents; @@ -25,6 +26,12 @@ * up to date after initialization. The SDK uses the first synchronizer and falls * back to subsequent ones if it encounters errors. *

+ * The FDv1 fallback synchronizer is an FDv1 polling synchronizer that the SDK + * switches to when LaunchDarkly directs it to fall back to FDv1. A customized mode keeps + * the SDK's default FDv1 fallback for that mode unless you configure it with + * {@link #fdv1FallbackSynchronizer(FDv1FallbackSynchronizerEntry)} or remove it with + * {@link #disableFdv1Fallback()}. + *

* Obtain an instance from {@link com.launchdarkly.sdk.android.DataSystemComponents#customMode()}, * configure it, and pass it to * {@link DataSystemBuilder#customizeConnectionMode(ConnectionMode, ConnectionModeBuilder)}: @@ -43,6 +50,9 @@ public class ConnectionModeBuilder { private final List initializerEntries = new ArrayList<>(); private final List synchronizerEntries = new ArrayList<>(); + @Nullable + private FDv1FallbackSynchronizerEntry fdv1FallbackSynchronizerEntry; + private boolean fdv1FallbackDisabled; /** * Sets the initializers for this connection mode. @@ -90,6 +100,43 @@ public final ConnectionModeBuilder synchronizers(@NonNull SynchronizerEntry... s return this; } + /** + * Configures the FDv1 fallback synchronizer for this connection mode, replacing the + * SDK's default for the mode. This undoes any earlier call to {@link #disableFdv1Fallback()}. + *

+ * Use {@link DataSystemComponents#fdv1FallbackSynchronizer()} to obtain an entry: + *


+     *     builder.fdv1FallbackSynchronizer(
+     *         DataSystemComponents.fdv1FallbackSynchronizer()
+     *             .pollIntervalMillis(600_000))
+     * 
+ * + * @param fdv1FallbackSynchronizer the FDv1 fallback synchronizer entry + * @return this builder + */ + public ConnectionModeBuilder fdv1FallbackSynchronizer( + @NonNull FDv1FallbackSynchronizerEntry fdv1FallbackSynchronizer) { + this.fdv1FallbackSynchronizerEntry = fdv1FallbackSynchronizer; + this.fdv1FallbackDisabled = false; + return this; + } + + /** + * Removes the FDv1 fallback synchronizer from this connection mode, so the SDK does not + * fall back to FDv1 in this mode. This undoes any earlier call to + * {@link #fdv1FallbackSynchronizer(FDv1FallbackSynchronizerEntry)}. + *

+ * If you neither call this method nor configure an FDv1 fallback synchronizer, the mode + * keeps the SDK's default FDv1 fallback. + * + * @return this builder + */ + public ConnectionModeBuilder disableFdv1Fallback() { + this.fdv1FallbackSynchronizerEntry = null; + this.fdv1FallbackDisabled = true; + return this; + } + /** * Returns the configured initializer entries as an unmodifiable list. * @@ -109,4 +156,23 @@ public List getInitializerEntries() { public List getSynchronizerEntries() { return Collections.unmodifiableList(synchronizerEntries); } + + /** + * Returns the configured FDv1 fallback synchronizer entry, or null if none was configured. + * + * @return the FDv1 fallback synchronizer entry, or null + */ + @Nullable + public FDv1FallbackSynchronizerEntry getFdv1FallbackSynchronizerEntry() { + return fdv1FallbackSynchronizerEntry; + } + + /** + * Returns true if {@link #disableFdv1Fallback()} removed the FDv1 fallback from this mode. + * + * @return true if the FDv1 fallback is disabled for this mode + */ + public boolean isFdv1FallbackDisabled() { + return fdv1FallbackDisabled; + } } diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/DataSystemBuilder.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/DataSystemBuilder.java index a96e96a4f..59e53aac5 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/DataSystemBuilder.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/DataSystemBuilder.java @@ -248,17 +248,27 @@ public Map buildModeTable(boolean disableBackgro for (Map.Entry entry : connectionModeOverrides.entrySet()) { ConnectionModeBuilder cmb = entry.getValue(); - // Carry forward the default FDv1 fallback synchronizer only when the - // override supplies at least one initializer or synchronizer. An entirely - // empty override means "do nothing in this mode" (like OFFLINE), so there is - // no synchronizer that could receive the x-ld-fd-fallback header and the - // fallback slot would never be reached by FDv2DataSource.runSynchronizers(). + // Give the mode an FDv1 fallback synchronizer only when the override supplies + // at least one initializer or synchronizer. An entirely empty override means + // "do nothing in this mode" (like OFFLINE), so there is no synchronizer that + // could receive the x-ld-fd-fallback header and the fallback slot would never + // be reached by FDv2DataSource.runSynchronizers(). Otherwise use the + // override's FDv1 fallback entry, or the default for the mode if it configures + // none, and no fallback if it disables the FDv1 fallback. DataSourceBuilder fdv1FallbackSynchronizer = null; - if (!cmb.getInitializerEntries().isEmpty() || !cmb.getSynchronizerEntries().isEmpty()) { - ModeDefinition defaultForMode = table.get(entry.getKey()); - fdv1FallbackSynchronizer = defaultForMode != null - ? defaultForMode.getFdv1FallbackSynchronizer() - : null; + boolean hasDataSources = + !cmb.getInitializerEntries().isEmpty() || !cmb.getSynchronizerEntries().isEmpty(); + if (hasDataSources && !cmb.isFdv1FallbackDisabled()) { + FDv1FallbackSynchronizerEntry fdv1FallbackEntry = cmb.getFdv1FallbackSynchronizerEntry(); + if (fdv1FallbackEntry != null) { + fdv1FallbackSynchronizer = + FDv2EntryConverter.toFdv1FallbackSynchronizerBuilder(fdv1FallbackEntry); + } else { + ModeDefinition defaultForMode = table.get(entry.getKey()); + fdv1FallbackSynchronizer = defaultForMode != null + ? defaultForMode.getFdv1FallbackSynchronizer() + : null; + } } List> initializerBuilders = diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/FDv1FallbackSynchronizerEntry.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/FDv1FallbackSynchronizerEntry.java new file mode 100644 index 000000000..76c5d7156 --- /dev/null +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/FDv1FallbackSynchronizerEntry.java @@ -0,0 +1,70 @@ +package com.launchdarkly.sdk.android.integrations; + +import com.launchdarkly.sdk.android.LDConfig; +import com.launchdarkly.sdk.android.interfaces.ServiceEndpoints; + +import androidx.annotation.Nullable; + +/** + * Entry for the FDv1 fallback synchronizer of a connection mode. + *

+ * The SDK switches a mode to this FDv1 polling synchronizer when LaunchDarkly directs it + * to fall back to FDv1. It is not one of the mode's synchronizers; set it with + * {@link ConnectionModeBuilder#fdv1FallbackSynchronizer(FDv1FallbackSynchronizerEntry)}. + *

+ * Early access; not stable. + */ +public final class FDv1FallbackSynchronizerEntry { + + private int pollIntervalMillis = LDConfig.DEFAULT_POLL_INTERVAL_MILLIS; + + @Nullable + private ServiceEndpoints serviceEndpointsOverride; + + public FDv1FallbackSynchronizerEntry() { + } + + /** + * Sets the poll interval of the FDv1 fallback synchronizer. The default and the minimum + * are {@link LDConfig#DEFAULT_POLL_INTERVAL_MILLIS}. + * + * @param pollIntervalMillis the poll interval in milliseconds + * @return this entry + */ + public FDv1FallbackSynchronizerEntry pollIntervalMillis(int pollIntervalMillis) { + this.pollIntervalMillis = Math.max(pollIntervalMillis, LDConfig.DEFAULT_POLL_INTERVAL_MILLIS); + return this; + } + + /** + * Overrides service endpoints for the FDv1 fallback synchronizer, or {@code null} to use + * the SDK's polling endpoint. + * + * @param endpoints the service endpoints, or null + * @return this entry + */ + public FDv1FallbackSynchronizerEntry serviceEndpointsOverride(@Nullable ServiceEndpoints endpoints) { + this.serviceEndpointsOverride = endpoints; + return this; + } + + /** + * Overrides service endpoints for the FDv1 fallback synchronizer using a builder. + * + * @param serviceEndpointsBuilder the service endpoints builder + * @return this entry + */ + public FDv1FallbackSynchronizerEntry serviceEndpointsOverride(ServiceEndpointsBuilder serviceEndpointsBuilder) { + this.serviceEndpointsOverride = serviceEndpointsBuilder.createServiceEndpoints(); + return this; + } + + public int getPollIntervalMillis() { + return pollIntervalMillis; + } + + @Nullable + public ServiceEndpoints getServiceEndpointsOverride() { + return serviceEndpointsOverride; + } +} diff --git a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemBuilderTest.java b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemBuilderTest.java index 0195ec4c1..ae7280e2c 100644 --- a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemBuilderTest.java +++ b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemBuilderTest.java @@ -121,6 +121,37 @@ public void customizeConnectionMode_nullsFdv1FallbackWhenModeIsEmpty() { assertNull(streaming.getFdv1FallbackSynchronizer()); } + @Test + public void customizeConnectionMode_disableFdv1FallbackRemovesFallbackFromThatModeOnly() { + DataSystemBuilder b = Components.dataSystem() + .customizeConnectionMode( + ConnectionMode.STREAMING, + DataSystemComponents.customMode() + .synchronizers(DataSystemComponents.streamingSynchronizer()) + .disableFdv1Fallback()) + .customizeConnectionMode( + ConnectionMode.POLLING, + DataSystemComponents.customMode() + .synchronizers(DataSystemComponents.pollingSynchronizer())); + Map table = b.buildModeTable(false); + assertNull(table.get(ConnectionMode.STREAMING).getFdv1FallbackSynchronizer()); + assertNotNull(table.get(ConnectionMode.POLLING).getFdv1FallbackSynchronizer()); + assertNotNull(table.get(ConnectionMode.BACKGROUND).getFdv1FallbackSynchronizer()); + } + + @Test + public void customizeConnectionMode_configuredFdv1FallbackAppliesToModeWithoutDefault() { + // ONE_SHOT has no FDv1 fallback by default. + DataSystemBuilder b = Components.dataSystem() + .customizeConnectionMode( + ConnectionMode.ONE_SHOT, + DataSystemComponents.customMode() + .initializers(DataSystemComponents.pollingInitializer()) + .fdv1FallbackSynchronizer(DataSystemComponents.fdv1FallbackSynchronizer())); + Map table = b.buildModeTable(false); + assertNotNull(table.get(ConnectionMode.ONE_SHOT).getFdv1FallbackSynchronizer()); + } + @Test public void getConnectionModeOverrides_isUnmodifiable() { DataSystemBuilder b = Components.dataSystem(); diff --git a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemComponentsTest.java b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemComponentsTest.java index ed30edac1..278df9d7f 100644 --- a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemComponentsTest.java +++ b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DataSystemComponentsTest.java @@ -18,8 +18,8 @@ import org.junit.rules.Timeout; import java.util.Collections; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; @@ -29,7 +29,8 @@ /** * Tests the request wiring of the data source builder implementations in * {@link DataSystemComponents}: which HTTP method, path, and body each FDv2 - * component and the FDv1 fallback synchronizer use for a given HTTP configuration. + * component and the FDv1 fallback synchronizer use for a given HTTP configuration, and + * which endpoint and poll interval a mode's configured FDv1 fallback synchronizer uses. */ public class DataSystemComponentsTest { @@ -39,7 +40,7 @@ public class DataSystemComponentsTest { @Rule public TemporaryFolder cacheDir = new TemporaryFolder(); - private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); + private final FixedDelayRecordingExecutor executor = new FixedDelayRecordingExecutor(); private static final LDContext CONTEXT = LDContext.create("test-context-key"); private static final LDLogger LOGGER = LDLogger.none(); @@ -60,6 +61,22 @@ public void tearDown() { executor.shutdownNow(); } + /** Records the delay of the last fixed-delay task scheduled on it. */ + private static final class FixedDelayRecordingExecutor extends ScheduledThreadPoolExecutor { + volatile long lastFixedDelayMillis = -1; + + FixedDelayRecordingExecutor() { + super(2); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + lastFixedDelayMillis = unit.toMillis(delay); + return super.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + } + private static HttpConfiguration httpConfig(boolean useReport, boolean usePost) { return new HttpConfiguration(10_000, Collections.emptyMap(), null, useReport, usePost); } @@ -178,4 +195,65 @@ public void fdv1FallbackSynchronizerSendsGetByDefault() throws Exception { } } } + + @Test + public void fdv1FallbackSynchronizerConfiguredForModeUsesServiceEndpointsOverride() throws Exception { + try (HttpServer sdkPollingServer = HttpServer.start(Handlers.bodyJson(VALID_FDV1_JSON)); + HttpServer overrideServer = HttpServer.start(Handlers.bodyJson(VALID_FDV1_JSON))) { + ModeDefinition streaming = Components.dataSystem() + .customizeConnectionMode(ConnectionMode.STREAMING, + DataSystemComponents.customMode() + .synchronizers(DataSystemComponents.streamingSynchronizer()) + .fdv1FallbackSynchronizer(DataSystemComponents.fdv1FallbackSynchronizer() + .serviceEndpointsOverride(Components.serviceEndpoints() + .polling(overrideServer.getUri())))) + .buildModeTable(false) + .get(ConnectionMode.STREAMING); + + // Start the mode's FDv1 fallback synchronizer and wait for its first poll. + Synchronizer sync = streaming.getFdv1FallbackSynchronizer() + .build(makeInputs(sdkPollingServer, httpConfig(false, false))); + try { + sync.next().get(5, TimeUnit.SECONDS); + + // The poll went to the override endpoint and not to the SDK's polling endpoint. + RequestInfo req = overrideServer.getRecorder().requireRequest(); + assertTrue("path should use the FDv1 GET endpoint with a context segment", + req.getPath().startsWith("/msdk/evalx/contexts/")); + assertEquals(0, sdkPollingServer.getRecorder().count()); + } finally { + sync.close(); + } + } + } + + @Test + public void fdv1FallbackSynchronizerConfiguredForModeUsesPollIntervalAndSdkPollingEndpoint() throws Exception { + try (HttpServer server = HttpServer.start(Handlers.bodyJson(VALID_FDV1_JSON))) { + ModeDefinition streaming = Components.dataSystem() + .customizeConnectionMode(ConnectionMode.STREAMING, + DataSystemComponents.customMode() + .synchronizers(DataSystemComponents.streamingSynchronizer()) + .fdv1FallbackSynchronizer(DataSystemComponents.fdv1FallbackSynchronizer() + .pollIntervalMillis(600_000))) + .buildModeTable(false) + .get(ConnectionMode.STREAMING); + + // Start the mode's FDv1 fallback synchronizer and wait for its first poll. + Synchronizer sync = streaming.getFdv1FallbackSynchronizer() + .build(makeInputs(server, httpConfig(false, false))); + try { + sync.next().get(5, TimeUnit.SECONDS); + + // Without an endpoints override the poll goes to the SDK's polling endpoint, + // and the synchronizer polls at the configured interval. + RequestInfo req = server.getRecorder().requireRequest(); + assertTrue("path should use the FDv1 GET endpoint with a context segment", + req.getPath().startsWith("/msdk/evalx/contexts/")); + assertEquals(600_000, executor.lastFixedDelayMillis); + } finally { + sync.close(); + } + } + } } diff --git a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilderTest.java b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilderTest.java index de4ae6f4a..6259727da 100644 --- a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilderTest.java +++ b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/ConnectionModeBuilderTest.java @@ -1,7 +1,10 @@ package com.launchdarkly.sdk.android.integrations; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import com.launchdarkly.sdk.android.DataSystemComponents; @@ -59,4 +62,30 @@ public void freshBuilder_hasEmptyLists() { assertEquals(0, b.getInitializerEntries().size()); assertEquals(0, b.getSynchronizerEntries().size()); } + + @Test + public void freshBuilder_neitherConfiguresNorDisablesFdv1Fallback() { + ConnectionModeBuilder b = DataSystemComponents.customMode(); + assertNull(b.getFdv1FallbackSynchronizerEntry()); + assertFalse(b.isFdv1FallbackDisabled()); + } + + @Test + public void disableFdv1Fallback_replacesConfiguredFdv1FallbackSynchronizer() { + ConnectionModeBuilder b = DataSystemComponents.customMode() + .fdv1FallbackSynchronizer(DataSystemComponents.fdv1FallbackSynchronizer()) + .disableFdv1Fallback(); + assertNull(b.getFdv1FallbackSynchronizerEntry()); + assertTrue(b.isFdv1FallbackDisabled()); + } + + @Test + public void fdv1FallbackSynchronizer_replacesEarlierDisable() { + FDv1FallbackSynchronizerEntry fallback = DataSystemComponents.fdv1FallbackSynchronizer(); + ConnectionModeBuilder b = DataSystemComponents.customMode() + .disableFdv1Fallback() + .fdv1FallbackSynchronizer(fallback); + assertSame(fallback, b.getFdv1FallbackSynchronizerEntry()); + assertFalse(b.isFdv1FallbackDisabled()); + } }