Skip to content

Commit f0a1993

Browse files
authored
fix: Respect graceful mode on init (from sdk-common-jvm) (#88)
* chore:update to the latest common package * Tests for default assignments on failed client, no exceptions thrown
1 parent fbd764f commit f0a1993

File tree

5 files changed

+65
-3
lines changed

5 files changed

+65
-3
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test-data:
4040
mkdir -p ${tempDir}
4141
git clone -b ${branchName} --depth 1 --single-branch ${githubRepoLink} ${gitDataDir}
4242
cp -r ${gitDataDir}/ufc ${testDataDir}
43-
rm ${testDataDir}/ufc/bandit-tests/*.dynamic-typing.json
43+
rm -f ${testDataDir}/ufc/bandit-tests/*.dynamic-typing.json
4444
rm -rf ${tempDir}
4545

4646
.PHONY: test

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
```groovy
1111
dependencies {
12-
implementation 'cloud.eppo:eppo-server-sdk:3.1.0'
12+
implementation 'cloud.eppo:eppo-server-sdk:4.0.1'
1313
}
1414
```
1515

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ java {
1111
}
1212

1313
group = 'cloud.eppo'
14-
version = '4.0.0-SNAPSHOT'
14+
version = '4.0.1'
1515
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
1616

1717
import org.apache.tools.ant.filters.ReplaceTokens

src/main/java/cloud/eppo/EppoClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ public EppoClient buildAndInit() {
198198
pollingIntervalMs / DEFAULT_JITTER_INTERVAL_RATIO);
199199

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

203205
return instance;

src/test/java/cloud/eppo/EppoClientTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
2424
import java.io.File;
2525
import java.lang.reflect.Field;
26+
import java.util.concurrent.CompletableFuture;
2627
import java.util.stream.Stream;
2728
import org.apache.commons.io.FileUtils;
2829
import org.junit.jupiter.api.AfterAll;
@@ -238,6 +239,39 @@ public void testPolling() {
238239
verify(httpClientSpy, times(2)).get(anyString());
239240
}
240241

242+
// NOTE: Graceful mode during init is intrinsically true since the call is non-blocking and
243+
// exceptions are caught without rethrowing in `FetchConfigurationsTask`
244+
245+
@Test
246+
public void testClientMakesDefaultAssignmentsAfterFailingToInitialize() {
247+
// Set up bad HTTP response
248+
mockHttpError();
249+
250+
// Initialize and no exception should be thrown.
251+
try {
252+
EppoClient eppoClient = initFailingGracefulClient(true);
253+
Thread.sleep(25); // Sleep to allow the async config fetch call to happen (and fail)
254+
assertEquals("default", eppoClient.getStringAssignment("experiment1", "subject1", "default"));
255+
} catch (Exception e) {
256+
fail("Unexpected exception: " + e);
257+
}
258+
}
259+
260+
public static void mockHttpError() {
261+
// Create a mock instance of EppoHttpClient
262+
EppoHttpClient mockHttpClient = mock(EppoHttpClient.class);
263+
264+
// Mock sync get
265+
when(mockHttpClient.get(anyString())).thenThrow(new RuntimeException("Intentional Error"));
266+
267+
// Mock async get
268+
CompletableFuture<byte[]> mockAsyncResponse = new CompletableFuture<>();
269+
when(mockHttpClient.getAsync(anyString())).thenReturn(mockAsyncResponse);
270+
mockAsyncResponse.completeExceptionally(new RuntimeException("Intentional Error"));
271+
272+
setBaseClientHttpClientOverrideField(mockHttpClient);
273+
}
274+
241275
@SuppressWarnings("SameParameterValue")
242276
private void sleepUninterruptedly(long sleepMs) {
243277
try {
@@ -261,6 +295,20 @@ private EppoClient initClient(String apiKey) {
261295
.buildAndInit();
262296
}
263297

298+
private EppoClient initFailingGracefulClient(boolean isGracefulMode) {
299+
mockAssignmentLogger = mock(AssignmentLogger.class);
300+
mockBanditLogger = mock(BanditLogger.class);
301+
302+
return new EppoClient.Builder()
303+
.apiKey(DUMMY_FLAG_API_KEY)
304+
.host("blag")
305+
.assignmentLogger(mockAssignmentLogger)
306+
.banditLogger(mockBanditLogger)
307+
.isGracefulMode(isGracefulMode)
308+
.forceReinitialize(true) // Useful for tests
309+
.buildAndInit();
310+
}
311+
264312
private void uninitClient() {
265313
try {
266314
Field httpClientOverrideField = EppoClient.class.getDeclaredField("instance");
@@ -281,4 +329,16 @@ private void initBuggyClient() {
281329
throw new RuntimeException(e);
282330
}
283331
}
332+
333+
public static void setBaseClientHttpClientOverrideField(EppoHttpClient httpClient) {
334+
// Uses reflection to set a static override field used for tests (e.g., httpClientOverride)
335+
try {
336+
Field httpClientOverrideField = BaseEppoClient.class.getDeclaredField("httpClientOverride");
337+
httpClientOverrideField.setAccessible(true);
338+
httpClientOverrideField.set(null, httpClient);
339+
httpClientOverrideField.setAccessible(false);
340+
} catch (NoSuchFieldException | IllegalAccessException e) {
341+
throw new RuntimeException(e);
342+
}
343+
}
284344
}

0 commit comments

Comments
 (0)