diff --git a/build.gradle b/build.gradle index a88c2143..be102e41 100755 --- a/build.gradle +++ b/build.gradle @@ -129,6 +129,10 @@ dependencies { implementation ("io.github.kawamuray.wasmtime:wasmtime-java:$wasmtime_version") + implementation("com.squareup.okhttp3:logging-interceptor:$okhttp_version") { + exclude group: "com.squareup.okhttp3", module: "okhttp" + } + compileOnly("org.projectlombok:lombok:$lombok_version") testAnnotationProcessor("org.projectlombok:lombok:$lombok_version") diff --git a/src/main/java/com/devcycle/sdk/server/common/api/IDVCApi.java b/src/main/java/com/devcycle/sdk/server/common/api/IDVCApi.java index dc00c45d..ca4f619e 100755 --- a/src/main/java/com/devcycle/sdk/server/common/api/IDVCApi.java +++ b/src/main/java/com/devcycle/sdk/server/common/api/IDVCApi.java @@ -1,6 +1,7 @@ package com.devcycle.sdk.server.common.api; import com.devcycle.sdk.server.common.model.*; +import com.devcycle.sdk.server.local.model.EventsBatch; import retrofit2.Call; import retrofit2.http.*; @@ -62,4 +63,15 @@ public interface IDVCApi { @Headers({"Content-Type:application/json"}) @GET("config/v1/server/{sdkToken}.json") Call getConfig(@Path("sdkToken") String sdkToken, @Header("If-None-Match") String etag); + + /** + * Post events to DevCycle for user + * + * @param eventsBatch (required) + * @return Call<DVCResponse> + */ + @Headers({"Content-Type:application/json"}) + @POST("v1/events/batch") + Call publishEvents(@Body EventsBatch eventsBatch); } + diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalApiClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalApiClient.java index a7af88ba..12858bb2 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalApiClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalApiClient.java @@ -2,6 +2,7 @@ import com.devcycle.sdk.server.common.api.IDVCApi; import com.devcycle.sdk.server.local.model.DVCLocalOptions; +import com.devcycle.sdk.server.local.model.FlushPayload; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude; @@ -16,6 +17,7 @@ public final class DVCLocalApiClient { private final OkHttpClient.Builder okBuilder; private final Retrofit.Builder adapterBuilder; + private final Retrofit.Builder eventsBuilder; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -44,9 +46,13 @@ private DVCLocalApiClient(DVCLocalOptions options) { adapterBuilder = new Retrofit.Builder() .baseUrl(url) .addConverterFactory(JacksonConverterFactory.create()); + + eventsBuilder = new Retrofit.Builder() + .baseUrl("https://events.devcycle.com") + .addConverterFactory(JacksonConverterFactory.create()); } - public DVCLocalApiClient(String apiKey, DVCLocalOptions options) { + public DVCLocalApiClient(String serverKey, DVCLocalOptions options) { this(options); } diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java index e8770283..e9e6ef43 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java @@ -6,6 +6,7 @@ import com.devcycle.sdk.server.common.model.*; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; import com.devcycle.sdk.server.local.managers.EnvironmentConfigManager; +import com.devcycle.sdk.server.local.managers.EventQueueManager; import com.devcycle.sdk.server.local.model.BucketedUserConfig; import com.devcycle.sdk.server.local.model.DVCLocalOptions; import com.fasterxml.jackson.annotation.JsonInclude; @@ -22,6 +23,8 @@ public final class DVCLocalClient { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private EventQueueManager eventQueueManager; + public DVCLocalClient(String serverKey) { this(serverKey, DVCLocalOptions.builder().build()); } @@ -30,6 +33,7 @@ public DVCLocalClient(String serverKey, DVCLocalOptions dvcOptions) { configManager = new EnvironmentConfigManager(serverKey, localBucketing, dvcOptions); this.serverKey = serverKey; OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + eventQueueManager = new EventQueueManager(serverKey, localBucketing, dvcOptions); } /** @@ -71,6 +75,13 @@ public Variable variable(User user, String key, T defaultValue) { System.out.println("Variable called before DVCClient has initialized, returning default value"); } + Variable defaultVariable = (Variable) Variable.builder() + .key(key) + .value(defaultValue) + .isDefaulted(true) + .reasonUsingDefaultValue("Variable not found") + .build(); + try { String userString = OBJECT_MAPPER.writeValueAsString(user); @@ -78,29 +89,22 @@ public Variable variable(User user, String key, T defaultValue) { if (bucketedUserConfig.variables.containsKey(key)) { Variable variable = bucketedUserConfig.variables.get(key); variable.setIsDefaulted(false); + eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableEvaluated").target(key).build(), bucketedUserConfig); return variable; + } else { + eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), bucketedUserConfig); + return defaultVariable; } } catch (JsonProcessingException e) { System.out.printf("Unable to parse JSON for Variable %s due to error: %s", key, e.toString()); } - Variable variable; - - variable = (Variable) Variable.builder() - .key(key) - .value(defaultValue) - .isDefaulted(true) - .reasonUsingDefaultValue("Variable not found") - .build(); - - // TODO queue events - // eventQueue.queueAggregateEvent( - // user, - // new Event(type: EventTypes.variableDefaulted, target: key), - // null - // ); - - return variable; + try { + eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), null); + } catch (JsonProcessingException e) { + System.out.printf("Unable to parse aggVariableDefaulted event for Variable %s due to error: %s", key, e.toString()); + } + return defaultVariable; } /** @@ -123,19 +127,11 @@ public Map allVariables(User user) throws JsonProcessingExcept * @param user (required) * @param event (required) */ - // TODO: Original return type should match the line below, uncomment once - // implemented and delete the void return - // public DVCResponse track(User user, Event event) throws DVCException { - public void track(User user, Event event) { + public void track(User user, Event event) throws Exception { validateUser(user); localBucketing.setPlatformData(user.getPlatformData().toString()); - UserAndEvents userAndEvents = UserAndEvents.builder() - .user(user) - .events(Collections.singletonList(event)) - .build(); - - // Call track method to append custom event to queue + eventQueueManager.queueEvent(user, event); } private void validateUser(User user) { diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalEventsApiClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalEventsApiClient.java new file mode 100644 index 00000000..4208cb63 --- /dev/null +++ b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalEventsApiClient.java @@ -0,0 +1,64 @@ +package com.devcycle.sdk.server.local.api; + +import com.devcycle.sdk.server.common.api.IDVCApi; +import com.devcycle.sdk.server.common.interceptor.AuthorizationHeaderInterceptor; +import com.devcycle.sdk.server.local.model.DVCLocalOptions; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.annotation.JsonInclude; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import okhttp3.logging.HttpLoggingInterceptor.Level; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import java.util.Objects; + +public final class DVCLocalEventsApiClient { + + private final OkHttpClient.Builder okBuilder; + private final Retrofit.Builder adapterBuilder; + private HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final String EVENTS_API_URL = "https://events.devcycle.com/"; + + private String eventsApiUrl; + + private DVCLocalEventsApiClient(DVCLocalOptions options) { + String url; + + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + okBuilder = new OkHttpClient.Builder(); + + String eventsApiUrlFromOptions = options.getEventsApiBaseUrl(); + + eventsApiUrl = checkIfStringNullOrEmpty(eventsApiUrlFromOptions) ? EVENTS_API_URL : eventsApiUrlFromOptions; + + url = this.eventsApiUrl; + + adapterBuilder = new Retrofit.Builder() + .baseUrl(url) + .addConverterFactory(JacksonConverterFactory.create()); + } + + public DVCLocalEventsApiClient(String serverKey, DVCLocalOptions options) { + this(options); + okBuilder.addInterceptor(new AuthorizationHeaderInterceptor(serverKey)); + logging.setLevel(Level.BODY); + + okBuilder.addInterceptor(logging); + } + + public IDVCApi initialize() { + return adapterBuilder + .client(okBuilder.build()) + .build() + .create(IDVCApi.class); + } + + private Boolean checkIfStringNullOrEmpty(String stringToCheck) { + return Objects.isNull(stringToCheck) || Objects.equals(stringToCheck, ""); + } +} diff --git a/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java b/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java index dc5f5ef0..f4f6cb99 100644 --- a/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java +++ b/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java @@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicReference; import com.devcycle.sdk.server.local.model.BucketedUserConfig; -import com.devcycle.sdk.server.local.model.EventPayload; +import com.devcycle.sdk.server.local.model.FlushPayload; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -180,7 +180,7 @@ public void queueAggregateEvent(String token, String event, String variableVaria fn.accept(tokenAddress, eventAddress, variableVariationMapAddress); } - public EventPayload[] flushEventQueue(String token) throws JsonProcessingException { + public FlushPayload[] flushEventQueue(String token) throws JsonProcessingException { int tokenAddress = newWasmString(token); Func flushEventQueuePtr = linker.get(store, "", "flushEventQueue").get().func(); @@ -197,7 +197,7 @@ public EventPayload[] flushEventQueue(String token) throws JsonProcessingExcepti df.setTimeZone(TimeZone.getTimeZone("UTC")); objectMapper.setDateFormat(df); - EventPayload[] payloads = objectMapper.readValue(flushPayloadsStr, EventPayload[].class); + FlushPayload[] payloads = objectMapper.readValue(flushPayloadsStr, FlushPayload[].class); return payloads; } diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index c1732038..84b583a8 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -31,20 +31,18 @@ public final class EnvironmentConfigManager { private ProjectConfig config; private String configETag = ""; - private String environmentKey; + private String serverKey; private int pollingIntervalMS; - public EnvironmentConfigManager(String environmentKey, LocalBucketing localBucketing, DVCLocalOptions options) { - this.environmentKey = environmentKey; + public EnvironmentConfigManager(String serverKey, LocalBucketing localBucketing, DVCLocalOptions options) { + this.serverKey = serverKey; this.localBucketing = localBucketing; - configApiClient = new DVCLocalApiClient(environmentKey, options).initialize(); + configApiClient = new DVCLocalApiClient(serverKey, options).initialize(); int configPollingIntervalMs = options.getConfigPollingIntervalMs(); pollingIntervalMS = configPollingIntervalMs >= MIN_INTERVALS_MS ? configPollingIntervalMs : DEFAULT_POLL_INTERVAL_MS; - - this.localBucketing = localBucketing; setupScheduler(); } @@ -68,7 +66,7 @@ public boolean isConfigInitialized() { } private ProjectConfig getConfig() throws DVCException, JsonProcessingException { - Call config = this.configApiClient.getConfig(this.environmentKey, this.configETag); + Call config = this.configApiClient.getConfig(this.serverKey, this.configETag); this.config = getConfigResponse(config); return this.config; @@ -93,7 +91,7 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce ProjectConfig config = response.body(); try { ObjectMapper mapper = new ObjectMapper(); - localBucketing.storeConfig(environmentKey, mapper.writeValueAsString(config)); + localBucketing.storeConfig(serverKey, mapper.writeValueAsString(config)); } catch (JsonProcessingException e) { if (this.config != null) { System.out.printf("Unable to parse config with etag: %s. Using cache, etag %s%n", currentETag, this.configETag); diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java index d39de71e..afef8af8 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java @@ -1,21 +1,144 @@ package com.devcycle.sdk.server.local.managers; +import com.devcycle.sdk.server.common.api.IDVCApi; +import com.devcycle.sdk.server.common.model.*; +import com.devcycle.sdk.server.local.api.DVCLocalEventsApiClient; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; +import com.devcycle.sdk.server.local.model.*; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import retrofit2.Call; +import retrofit2.Response; + +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; public class EventQueueManager { - private final LocalBucketing localBucketing; + private LocalBucketing localBucketing; + private final String serverKey; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private IDVCApi eventsApiClient; + private int eventFlushIntervalMS; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private boolean isFlushingEvents = false; - public EventQueueManager(LocalBucketing localBucketing) { + public EventQueueManager(String serverKey, LocalBucketing localBucketing, DVCLocalOptions options) { this.localBucketing = localBucketing; + this.serverKey = serverKey; + eventFlushIntervalMS = options.getEventFlushIntervalMS(); + + this.localBucketing.initEventQueue(serverKey, "{}"); + + this.localBucketing.setPlatformData(User.builder().userId("java-server-sdk").build().getPlatformData().toString()); + eventsApiClient = new DVCLocalEventsApiClient(serverKey, options).initialize(); + + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + setupScheduler(); } - // TODO copy https://github.com/DevCycleHQ/js-sdks/blob/629b1f0a0d2cbed36bfe364d69f1929f645046db/sdk/nodejs/src/eventQueueAS.ts#L53 - // to fill in these functions, then call from DVCLocalClient + private void setupScheduler() { + Runnable getConfigRunnable = new Runnable() { + public void run() { + try { + flushEvents(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + scheduler.scheduleAtFixedRate(getConfigRunnable, 0, this.eventFlushIntervalMS, TimeUnit.MILLISECONDS); + } + + /** + * Flush events in queue to DevCycle Events API. Requeue events if flush fails + */ //flushEvents + public void flushEvents() throws Exception { + if (isFlushingEvents) return; + + if (serverKey == null || serverKey.equals("")) { + throw new Exception("DevCycle is not yet initialized to publish events."); + } + + FlushPayload[] flushPayloads = new FlushPayload[0]; + try { + flushPayloads = this.localBucketing.flushEventQueue(this.serverKey); + } catch (Exception e) { + System.out.printf("DVC Error Flushing Events: %s%n", e.getMessage()); + } + + if (flushPayloads.length == 0) return; + System.out.printf("AS Flush Payloads: %s%n", Arrays.toString(flushPayloads)); + + int eventCount = 0; + isFlushingEvents = true; + for (FlushPayload payload: flushPayloads) { + eventCount += payload.eventCount; + publishEvents(this.serverKey, payload); + } + isFlushingEvents = false; + System.out.printf("DVC Flush %d AS Events, for %d Users%n", eventCount, flushPayloads.length); + } + + /** + * Queue DVCAPIEvent for publishing to DevCycle Events API. + */ + public void queueEvent(User user, Event event) throws JsonProcessingException { + this.localBucketing.queueEvent(this.serverKey, OBJECT_MAPPER.writeValueAsString(user), OBJECT_MAPPER.writeValueAsString(event)); + } + + /** + * Queue DVCEvent that can be aggregated together, where multiple calls are aggregated + * by incrementing the 'value' field. + */ + public void queueAggregateEvent(Event event, BucketedUserConfig bucketedConfig) throws JsonProcessingException { + if (bucketedConfig != null) { + this.localBucketing.queueAggregateEvent(this.serverKey, OBJECT_MAPPER.writeValueAsString(event), OBJECT_MAPPER.writeValueAsString(bucketedConfig.variableVariationMap)); + } else { + this.localBucketing.queueAggregateEvent(this.serverKey, OBJECT_MAPPER.writeValueAsString(event), "{}"); + } + } + + private void publishEvents(String serverKey, FlushPayload flushPayload) throws InterruptedException { + Thread publishEventsThread = new Thread(new Runnable() { + @Override + public void run() { + Call response = eventsApiClient.publishEvents(EventsBatch.builder().batch(flushPayload.records).build()); + int responseCode = getResponse(response); - //queueEvent + if (responseCode == 201) { + localBucketing.onPayloadSuccess(serverKey, flushPayload.payloadId); + } else { + System.out.printf("DVC Error Publishing Events: %d%n", responseCode); + localBucketing.onPayloadFailure(serverKey, flushPayload.payloadId, responseCode >= 500); + } + } + }); - //queueAggregateEvent + publishEventsThread.start(); + publishEventsThread.join(); + } + + private int getResponse(Call call) { + Response response = null; + + try { + response = call.execute(); + } catch (IOException e) { + System.out.printf("DVC Events error: %s%n", e.getMessage()); + } + + if (response == null) { + return 500; + } else { + return response.code(); + } + } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/DVCLocalOptions.java b/src/main/java/com/devcycle/sdk/server/local/model/DVCLocalOptions.java index 2aea090c..99fb3316 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/DVCLocalOptions.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/DVCLocalOptions.java @@ -17,5 +17,11 @@ public class DVCLocalOptions { @Builder.Default private String configCdnBaseUrl = "https://config-cdn.devcycle.com/"; + @Builder.Default + private String eventsApiBaseUrl = "https://events.devcycle.com/"; + + @Builder.Default + private int eventFlushIntervalMS = 10000; + public static class DVCLocalOptionsBuilder implements IDVCOptions { } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/EventPayload.java b/src/main/java/com/devcycle/sdk/server/local/model/EventPayload.java index cd352004..1dc01d01 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/EventPayload.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/EventPayload.java @@ -1,15 +1,9 @@ package com.devcycle.sdk.server.local.model; +import com.devcycle.sdk.server.common.model.Event; import com.devcycle.sdk.server.common.model.User; public class EventPayload { - public Record[] records; - public String payloadId; - public int eventCount; - - public static class Record { - public User user; - public RequestEvent[] events; - } + private User user; + private Event[] events; } - diff --git a/src/main/java/com/devcycle/sdk/server/local/model/EventsBatch.java b/src/main/java/com/devcycle/sdk/server/local/model/EventsBatch.java new file mode 100644 index 00000000..df0c3ead --- /dev/null +++ b/src/main/java/com/devcycle/sdk/server/local/model/EventsBatch.java @@ -0,0 +1,9 @@ +package com.devcycle.sdk.server.local.model; + +import lombok.Builder; + +@Builder +public class EventsBatch { + public FlushPayload.Record[] batch; +} + diff --git a/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java b/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java new file mode 100644 index 00000000..14e3a791 --- /dev/null +++ b/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java @@ -0,0 +1,34 @@ +package com.devcycle.sdk.server.local.model; + +import com.devcycle.sdk.server.common.model.User; + +import java.util.Arrays; + +public class FlushPayload { + public Record[] records; + public String payloadId; + public int eventCount; + + @Override + public String toString() { + return "EventPayload{" + + "records=" + Arrays.toString(records) + + ", payloadId='" + payloadId + '\'' + + ", eventCount=" + eventCount + + '}'; + } + + public static class Record { + @Override + public String toString() { + return "Record{" + + "user=" + user + + ", events=" + Arrays.toString(events) + + '}'; + } + + public User user; + public RequestEvent[] events; + } +} + diff --git a/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java b/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java index 5e06cb75..840fe173 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java @@ -1,8 +1,7 @@ package com.devcycle.sdk.server.local.model; -import com.devcycle.sdk.server.local.utils.LongTimestampDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -10,6 +9,7 @@ import lombok.NoArgsConstructor; import java.math.BigDecimal; +import java.util.Date; import java.util.Map; @Data @@ -32,12 +32,12 @@ public class RequestEvent { private String target; @Schema(description = "Unix epoch time the event occurred according to client") - @JsonDeserialize(using = LongTimestampDeserializer.class) - private Long date; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'") + private String date; @Schema(description = "Unix epoch time the event occurred according to client") - @JsonDeserialize(using = LongTimestampDeserializer.class) - private Long clientDate; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'") + private Date clientDate; @Schema(description = "Value for numerical events. Contextual to event type") private BigDecimal value; diff --git a/src/main/resources/bucketing-lib.release.wasm b/src/main/resources/bucketing-lib.release.wasm index 1fb3f44d..c966e54c 100644 Binary files a/src/main/resources/bucketing-lib.release.wasm and b/src/main/resources/bucketing-lib.release.wasm differ diff --git a/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java b/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java index 996c611a..847ffabe 100644 --- a/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java +++ b/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java @@ -2,6 +2,7 @@ import com.devcycle.sdk.server.helpers.TestResponse; import com.devcycle.sdk.server.common.model.*; +import com.devcycle.sdk.server.local.model.EventsBatch; import retrofit2.Call; import java.util.Map; @@ -33,4 +34,10 @@ public Call getConfig(String sdkToken, String etag) { // TODO Auto-generated method stub return null; } + + @Override + public Call publishEvents(EventsBatch eventsBatch) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/test/java/com/devcycle/sdk/server/local/DVCLocalClientTest.java b/src/test/java/com/devcycle/sdk/server/local/DVCLocalClientTest.java index b64ce499..c04af5e0 100644 --- a/src/test/java/com/devcycle/sdk/server/local/DVCLocalClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/local/DVCLocalClientTest.java @@ -3,6 +3,11 @@ import java.util.Map; import java.util.UUID; +import com.devcycle.sdk.server.common.api.IDVCApi; +import com.devcycle.sdk.server.common.model.Event; +import com.devcycle.sdk.server.local.api.DVCLocalEventsApiClient; +import com.devcycle.sdk.server.local.managers.EventQueueManager; +import com.devcycle.sdk.server.local.model.DVCLocalOptions; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -23,13 +28,17 @@ public class DVCLocalClientTest { private DVCLocalClient client; final String testConfigString = "{\"project\":{\"_id\":\"61f97628ff4afcb6d057dbf0\",\"key\":\"emma-project\",\"a0_organization\":\"org_tPyJN5dvNNirKar7\",\"settings\":{\"edgeDB\":{\"enabled\":false},\"optIn\":{\"enabled\":true,\"title\":\"EarlyAccess\",\"description\":\"Getearlyaccesstobetafeaturesbelow!\",\"imageURL\":\"\",\"colors\":{\"primary\":\"#531cd9\",\"secondary\":\"#16dec0\"}}}},\"environment\":{\"_id\":\"61f97628ff4afcb6d057dbf2\",\"key\":\"development\"},\"features\":[{\"_id\":\"62fbf6566f1ba302829f9e32\",\"key\":\"a-cool-new-feature\",\"type\":\"release\",\"variations\":[{\"key\":\"variation-on\",\"name\":\"VariationOn\",\"variables\":[{\"_var\":\"62fbf6566f1ba302829f9e34\",\"value\":true},{\"_var\":\"63125320a4719939fd57cb2b\",\"value\":\"variationOff\"}],\"_id\":\"62fbf6566f1ba302829f9e38\"},{\"key\":\"variation-off\",\"name\":\"VariationOff\",\"variables\":[{\"_var\":\"62fbf6566f1ba302829f9e34\",\"value\":false},{\"_var\":\"63125320a4719939fd57cb2b\",\"value\":\"variationOn\"}],\"_id\":\"62fbf6566f1ba302829f9e39\"}],\"configuration\":{\"_id\":\"62fbf6576f1ba302829f9e4d\",\"targets\":[{\"_audience\":{\"_id\":\"63125321d31c601f992288b6\",\"filters\":{\"filters\":[{\"type\":\"user\",\"subType\":\"email\",\"comparator\":\"=\",\"values\":[\"giveMeVariationOff@email.com\"],\"filters\":[]}],\"operator\":\"and\"}},\"distribution\":[{\"_variation\":\"62fbf6566f1ba302829f9e38\",\"percentage\":1}],\"_id\":\"63125321d31c601f992288bb\"},{\"_audience\":{\"_id\":\"63125321d31c601f992288b7\",\"filters\":{\"filters\":[{\"type\":\"all\",\"values\":[],\"filters\":[]}],\"operator\":\"and\"}},\"distribution\":[{\"_variation\":\"62fbf6566f1ba302829f9e39\",\"percentage\":1}],\"_id\":\"63125321d31c601f992288bc\"}],\"forcedUsers\":{}}}],\"variables\":[{\"_id\":\"62fbf6566f1ba302829f9e34\",\"key\":\"a-cool-new-feature\",\"type\":\"Boolean\"},{\"_id\":\"63125320a4719939fd57cb2b\",\"key\":\"string-var\",\"type\":\"String\"}],\"variableHashes\":{\"a-cool-new-feature\":1868656757,\"string-var\":2413071944}}"; final String apiKey = String.format("server-%s", UUID.randomUUID()); + private LocalBucketing localBucketing; + private EventQueueManager eventQueueManager; @Before public void setup() { client = new DVCLocalClient(apiKey); - LocalBucketing localBucketing = new LocalBucketing(); + localBucketing = new LocalBucketing(); localBucketing.storeConfig(apiKey, testConfigString); + eventQueueManager = new EventQueueManager(apiKey, localBucketing, DVCLocalOptions.builder().build()); WhiteBox.setInternalState(client, "localBucketing", localBucketing); + WhiteBox.setInternalState(client, "eventQueueManager", eventQueueManager); } @Test public void variableTest() {