Skip to content

Commit 2b31439

Browse files
authored
feat: use baseUrl instead of host in a backwards-compatible way (#75)
* Add baseUrl constructor param/field * minor version bump for added param
1 parent 8cdb2f4 commit 2b31439

File tree

8 files changed

+90
-26
lines changed

8 files changed

+90
-26
lines changed

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group = 'cloud.eppo'
9-
version = '3.5.5-SNAPSHOT'
9+
version = '3.6.0'
1010
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
1111

1212
java {
@@ -25,6 +25,7 @@ dependencies {
2525
implementation 'org.slf4j:slf4j-api:2.0.16'
2626
testImplementation 'org.slf4j:slf4j-simple:2.0.16'
2727
testImplementation platform('org.junit:junit-bom:5.11.3')
28+
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
2829
testImplementation 'org.junit.jupiter:junit-jupiter'
2930
testImplementation 'org.skyscreamer:jsonassert:1.5.3'
3031
testImplementation 'commons-io:commons-io:2.18.0'

src/main/java/cloud/eppo/BaseEppoClient.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ public class BaseEppoClient {
5252
// It is important that the bandit assignment cache expire with a short-enough TTL to last about
5353
// one user session.
5454
// The recommended is 10 minutes (per @Sven)
55+
/** @param host To be removed in v4. use `apiBaseUrl` instead. */
5556
protected BaseEppoClient(
5657
@NotNull String apiKey,
5758
@NotNull String sdkName,
5859
@NotNull String sdkVersion,
59-
@Nullable String host,
60+
@Deprecated @Nullable String host,
61+
@Nullable String apiBaseUrl,
6062
@Nullable AssignmentLogger assignmentLogger,
6163
@Nullable BanditLogger banditLogger,
6264
@Nullable IConfigurationStore configurationStore,
@@ -74,14 +76,14 @@ protected BaseEppoClient(
7476
throw new IllegalArgumentException(
7577
"Unable to initialize Eppo SDK due to missing SDK name or version");
7678
}
77-
if (host == null) {
78-
host = Constants.DEFAULT_BASE_URL;
79+
if (apiBaseUrl == null) {
80+
apiBaseUrl = host != null ? Constants.appendApiPathToHost(host) : Constants.DEFAULT_BASE_URL;
7981
}
8082

8183
this.assignmentCache = assignmentCache;
8284
this.banditAssignmentCache = banditAssignmentCache;
8385

84-
EppoHttpClient httpClient = buildHttpClient(host, apiKey, sdkName, sdkVersion);
86+
EppoHttpClient httpClient = buildHttpClient(apiBaseUrl, apiKey, sdkName, sdkVersion);
8587
this.configurationStore =
8688
configurationStore != null ? configurationStore : new ConfigurationStore();
8789

@@ -103,10 +105,10 @@ protected BaseEppoClient(
103105
}
104106

105107
private EppoHttpClient buildHttpClient(
106-
String host, String apiKey, String sdkName, String sdkVersion) {
108+
String apiBaseUrl, String apiKey, String sdkName, String sdkVersion) {
107109
return httpClientOverride != null
108110
? httpClientOverride
109-
: new EppoHttpClient(host, apiKey, sdkName, sdkVersion);
111+
: new EppoHttpClient(apiBaseUrl, apiKey, sdkName, sdkVersion);
110112
}
111113

112114
protected void loadConfiguration() {

src/main/java/cloud/eppo/Constants.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
/** Constants Class */
44
public class Constants {
55
/** Base URL */
6-
public static final String DEFAULT_BASE_URL = "https://fscdn.eppo.cloud";
6+
public static final String DEFAULT_BASE_URL = "https://fscdn.eppo.cloud/api";
7+
8+
static String appendApiPathToHost(String host) {
9+
return host + "/api";
10+
}
711

812
public static final int REQUEST_TIMEOUT_MILLIS = 1000;
913

@@ -20,8 +24,8 @@ public class Constants {
2024
/** RAC settings */
2125
public static final String RAC_ENDPOINT = "/randomized_assignment/v3/config";
2226

23-
public static final String BANDIT_ENDPOINT = "/api/flag-config/v1/bandits";
24-
public static final String FLAG_CONFIG_ENDPOINT = "/api/flag-config/v1/config";
27+
public static final String BANDIT_ENDPOINT = "/flag-config/v1/bandits";
28+
public static final String FLAG_CONFIG_ENDPOINT = "/flag-config/v1/config";
2529

2630
/** Caching Settings */
2731
public static final String EXPERIMENT_CONFIGURATION_CACHE_KEY = "experiment-configuration";

src/main/java/cloud/eppo/api/Configuration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ public boolean isEmpty() {
156156
return flags == null || flags.isEmpty();
157157
}
158158

159+
public static Builder builder(byte[] flagJson) {
160+
return new Builder(flagJson);
161+
}
162+
163+
@Deprecated // isConfigObfuscated is determined from the byte payload
159164
public static Builder builder(byte[] flagJson, boolean isConfigObfuscated) {
160165
return new Builder(flagJson, isConfigObfuscated);
161166
}
@@ -186,10 +191,12 @@ private static FlagConfigResponse parseFlagResponse(byte[] flagJson) {
186191
}
187192
}
188193

194+
@Deprecated // isConfigObfuscated is determined from the byte payload
189195
public Builder(String flagJson, boolean isConfigObfuscated) {
190196
this(flagJson.getBytes(), parseFlagResponse(flagJson.getBytes()), isConfigObfuscated);
191197
}
192198

199+
@Deprecated // isConfigObfuscated is determined from the byte payload
193200
public Builder(byte[] flagJson, boolean isConfigObfuscated) {
194201
this(flagJson, parseFlagResponse(flagJson), isConfigObfuscated);
195202
}

src/test/java/cloud/eppo/BaseEppoClientBanditTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public static void initClient() {
6868
DUMMY_BANDIT_API_KEY,
6969
"java",
7070
"3.0.0",
71+
null,
7172
TEST_HOST,
7273
mockAssignmentLogger,
7374
mockBanditLogger,
@@ -98,6 +99,7 @@ private BaseEppoClient initClientWithData(
9899
DUMMY_BANDIT_API_KEY,
99100
"java",
100101
"3.0.0",
102+
null,
101103
TEST_HOST,
102104
mockAssignmentLogger,
103105
mockBanditLogger,

src/test/java/cloud/eppo/BaseEppoClientTest.java

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
import static cloud.eppo.helpers.TestUtils.mockHttpError;
66
import static cloud.eppo.helpers.TestUtils.mockHttpResponse;
77
import static cloud.eppo.helpers.TestUtils.setBaseClientHttpClientOverrideField;
8-
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
9-
import static org.junit.jupiter.api.Assertions.assertEquals;
10-
import static org.junit.jupiter.api.Assertions.assertFalse;
11-
import static org.junit.jupiter.api.Assertions.assertThrows;
12-
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
import static org.junit.jupiter.api.Assertions.*;
139
import static org.mockito.Mockito.*;
1410

1511
import cloud.eppo.api.*;
@@ -22,10 +18,14 @@
2218
import com.fasterxml.jackson.databind.ObjectMapper;
2319
import java.io.File;
2420
import java.io.IOException;
21+
import java.net.URL;
2522
import java.util.*;
2623
import java.util.concurrent.CompletableFuture;
2724
import java.util.concurrent.CompletionException;
2825
import java.util.stream.Stream;
26+
import okhttp3.mockwebserver.MockResponse;
27+
import okhttp3.mockwebserver.MockWebServer;
28+
import okhttp3.mockwebserver.RecordedRequest;
2929
import org.apache.commons.io.FileUtils;
3030
import org.junit.jupiter.api.BeforeEach;
3131
import org.junit.jupiter.api.Test;
@@ -42,10 +42,10 @@ public class BaseEppoClientTest {
4242

4343
// Use branch if specified by env variable `TEST_DATA_BRANCH`.
4444
private static final String TEST_BRANCH = System.getenv("TEST_DATA_BRANCH");
45-
private static final String TEST_HOST_BASE =
45+
private static final String TEST_API_CLOUD_FUNCTION_URL =
4646
"https://us-central1-eppo-qa.cloudfunctions.net/serveGitHubRacTestFile";
47-
private static final String TEST_HOST =
48-
TEST_HOST_BASE + (TEST_BRANCH != null ? "/b/" + TEST_BRANCH : "");
47+
private static final String TEST_BASE_URL =
48+
TEST_API_CLOUD_FUNCTION_URL + (TEST_BRANCH != null ? "/b/" + TEST_BRANCH : "") + "/api";
4949

5050
private final ObjectMapper mapper =
5151
new ObjectMapper().registerModule(AssignmentTestCase.assignmentTestCaseModule());
@@ -73,7 +73,8 @@ private void initClientWithData(
7373
DUMMY_FLAG_API_KEY,
7474
isConfigObfuscated ? "android" : "java",
7575
"100.1.0",
76-
TEST_HOST,
76+
null,
77+
TEST_BASE_URL,
7778
mockAssignmentLogger,
7879
null,
7980
null,
@@ -93,7 +94,8 @@ private void initClient(boolean isGracefulMode, boolean isConfigObfuscated) {
9394
DUMMY_FLAG_API_KEY,
9495
isConfigObfuscated ? "android" : "java",
9596
"100.1.0",
96-
TEST_HOST,
97+
null,
98+
TEST_BASE_URL,
9799
mockAssignmentLogger,
98100
null,
99101
null,
@@ -117,7 +119,8 @@ private CompletableFuture<Void> initClientAsync(
117119
DUMMY_FLAG_API_KEY,
118120
isConfigObfuscated ? "android" : "java",
119121
"100.1.0",
120-
TEST_HOST,
122+
null,
123+
TEST_BASE_URL,
121124
mockAssignmentLogger,
122125
null,
123126
null,
@@ -139,7 +142,8 @@ private void initClientWithAssignmentCache(IAssignmentCache cache) {
139142
DUMMY_FLAG_API_KEY,
140143
"java",
141144
"100.1.0",
142-
TEST_HOST,
145+
null,
146+
TEST_BASE_URL,
143147
mockAssignmentLogger,
144148
null,
145149
null,
@@ -180,6 +184,49 @@ private static Stream<Arguments> getAssignmentTestData() {
180184
return AssignmentTestCase.getAssignmentTestData();
181185
}
182186

187+
@Test
188+
public void testBaseUrlBackwardsCompatibility() throws IOException, InterruptedException {
189+
// Base client must be buildable with a HOST (i.e. no `/api` postfix)
190+
mockAssignmentLogger = mock(AssignmentLogger.class);
191+
192+
MockWebServer mockWebServer = new MockWebServer();
193+
URL mockServerBaseUrl = mockWebServer.url("").url(); // get base url of mockwebserver
194+
195+
// Remove trailing slash to mimic typical "host" parameter of "https://fscdn.eppo.cloud"
196+
String testHost = mockServerBaseUrl.toString().replaceAll("/$", "");
197+
198+
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{}"));
199+
200+
eppoClient =
201+
new BaseEppoClient(
202+
DUMMY_FLAG_API_KEY,
203+
"java",
204+
"100.1.0",
205+
testHost,
206+
null,
207+
mockAssignmentLogger,
208+
null,
209+
null,
210+
false,
211+
false,
212+
true,
213+
null,
214+
null,
215+
null);
216+
217+
eppoClient.loadConfiguration();
218+
219+
// Test what path the call was sent to
220+
RecordedRequest request = mockWebServer.takeRequest();
221+
assertNotNull(request);
222+
assertEquals("GET", request.getMethod());
223+
224+
// The "/api" part comes from appending it on to a "host" parameter but not a base URL param.
225+
assertEquals(
226+
"/api/flag-config/v1/config?apiKey=dummy-flags-api-key&sdkName=java&sdkVersion=100.1.0",
227+
request.getPath());
228+
}
229+
183230
@Test
184231
public void testErrorGracefulModeOn() throws JsonProcessingException {
185232
initClient(true, false);
@@ -287,7 +334,7 @@ public void testErrorGracefulModeOff() {
287334
@Test
288335
public void testInvalidConfigJSON() {
289336

290-
mockHttpResponse(TEST_HOST, "{}");
337+
mockHttpResponse(TEST_BASE_URL, "{}");
291338

292339
initClient(false, false);
293340

src/test/java/cloud/eppo/ConfigurationRequestorTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void testInitialConfigurationDoesntClobberFetch() throws IOException {
5858
String fetchedFlagConfig =
5959
FileUtils.readFileToString(differentFlagConfigFile, StandardCharsets.UTF_8);
6060

61-
when(mockHttpClient.getAsync("/api/flag-config/v1/config")).thenReturn(configFetchFuture);
61+
when(mockHttpClient.getAsync("/flag-config/v1/config")).thenReturn(configFetchFuture);
6262

6363
// Set initial config and verify that no config has been set yet.
6464
requestor.setInitialConfiguration(initialConfigFuture);
@@ -98,7 +98,7 @@ public void testBrokenFetchDoesntClobberCache() throws IOException {
9898
String flagConfig = FileUtils.readFileToString(initialFlagConfigFile, StandardCharsets.UTF_8);
9999
CompletableFuture<byte[]> configFetchFuture = new CompletableFuture<>();
100100

101-
when(mockHttpClient.getAsync("/api/flag-config/v1/config")).thenReturn(configFetchFuture);
101+
when(mockHttpClient.getAsync("/flag-config/v1/config")).thenReturn(configFetchFuture);
102102

103103
// Set initial config and verify that no config has been set yet.
104104
requestor.setInitialConfiguration(initialConfigFuture);
@@ -135,7 +135,7 @@ public void testCacheWritesAfterBrokenFetch() throws IOException {
135135
String flagConfig = FileUtils.readFileToString(initialFlagConfigFile, StandardCharsets.UTF_8);
136136
CompletableFuture<byte[]> configFetchFuture = new CompletableFuture<>();
137137

138-
when(mockHttpClient.getAsync("/api/flag-config/v1/config")).thenReturn(configFetchFuture);
138+
when(mockHttpClient.getAsync("/flag-config/v1/config")).thenReturn(configFetchFuture);
139139

140140
// Set initial config and verify that no config has been set yet.
141141
requestor.setInitialConfiguration(initialConfigFuture);

src/test/java/cloud/eppo/ProfileBaseEppoClientTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public static void initClient() {
3939
DUMMY_FLAG_API_KEY,
4040
"java",
4141
"3.0.0",
42+
null,
4243
TEST_HOST,
4344
noOpAssignmentLogger,
4445
null,

0 commit comments

Comments
 (0)