Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ test-data:
mkdir -p ${tempDir}
git clone -b ${branchName} --depth 1 --single-branch ${githubRepoLink} ${gitDataDir}
cp -r ${gitDataDir}/ufc ${testDataDir}
rm ${testDataDir}/ufc/bandit-tests/*.dynamic-typing.json
rm -f ${testDataDir}/ufc/bandit-tests/*.dynamic-typing.json
rm -rf ${tempDir}

.PHONY: test
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

```groovy
dependencies {
implementation 'cloud.eppo:eppo-server-sdk:3.1.0'
implementation 'cloud.eppo:eppo-server-sdk:4.0.1'
}
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ java {
}

group = 'cloud.eppo'
version = '4.0.0-SNAPSHOT'
version = '4.0.1'
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")

import org.apache.tools.ant.filters.ReplaceTokens
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/cloud/eppo/EppoClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ public EppoClient buildAndInit() {
pollingIntervalMs / DEFAULT_JITTER_INTERVAL_RATIO);

// Kick off the first fetch
// Graceful mode is implicit here because `FetchConfigurationsTask` catches and logs errors
// without rethrowing.
fetchConfigurationsTask.run();

return instance;
Expand Down
60 changes: 60 additions & 0 deletions src/test/java/cloud/eppo/EppoClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.io.File;
import java.lang.reflect.Field;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterAll;
Expand Down Expand Up @@ -238,6 +239,39 @@ public void testPolling() {
verify(httpClientSpy, times(2)).get(anyString());
}

// NOTE: Graceful mode during init is intrinsically true since the call is non-blocking and
// exceptions are caught without rethrowing in `FetchConfigurationsTask`

@Test
public void testClientMakesDefaultAssignmentsAfterFailingToInitialize() {
// Set up bad HTTP response
mockHttpError();

// Initialize and no exception should be thrown.
try {
EppoClient eppoClient = initFailingGracefulClient(true);
Thread.sleep(25); // Sleep to allow the async config fetch call to happen (and fail)
assertEquals("default", eppoClient.getStringAssignment("experiment1", "subject1", "default"));
} catch (Exception e) {
fail("Unexpected exception: " + e);
}
}

public static void mockHttpError() {
// Create a mock instance of EppoHttpClient
EppoHttpClient mockHttpClient = mock(EppoHttpClient.class);

// Mock sync get
when(mockHttpClient.get(anyString())).thenThrow(new RuntimeException("Intentional Error"));

// Mock async get
CompletableFuture<byte[]> mockAsyncResponse = new CompletableFuture<>();
when(mockHttpClient.getAsync(anyString())).thenReturn(mockAsyncResponse);
mockAsyncResponse.completeExceptionally(new RuntimeException("Intentional Error"));

setBaseClientHttpClientOverrideField(mockHttpClient);
}

@SuppressWarnings("SameParameterValue")
private void sleepUninterruptedly(long sleepMs) {
try {
Expand All @@ -261,6 +295,20 @@ private EppoClient initClient(String apiKey) {
.buildAndInit();
}

private EppoClient initFailingGracefulClient(boolean isGracefulMode) {
mockAssignmentLogger = mock(AssignmentLogger.class);
mockBanditLogger = mock(BanditLogger.class);

return new EppoClient.Builder()
.apiKey(DUMMY_FLAG_API_KEY)
.host("blag")
.assignmentLogger(mockAssignmentLogger)
.banditLogger(mockBanditLogger)
.isGracefulMode(isGracefulMode)
.forceReinitialize(true) // Useful for tests
.buildAndInit();
}

private void uninitClient() {
try {
Field httpClientOverrideField = EppoClient.class.getDeclaredField("instance");
Expand All @@ -281,4 +329,16 @@ private void initBuggyClient() {
throw new RuntimeException(e);
}
}

public static void setBaseClientHttpClientOverrideField(EppoHttpClient httpClient) {
// Uses reflection to set a static override field used for tests (e.g., httpClientOverride)
try {
Field httpClientOverrideField = BaseEppoClient.class.getDeclaredField("httpClientOverride");
httpClientOverrideField.setAccessible(true);
httpClientOverrideField.set(null, httpClient);
httpClientOverrideField.setAccessible(false);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
Loading