Skip to content

Commit 1ee6977

Browse files
committed
feat: added initialization option for hooks
1 parent 425ef03 commit 1ee6977

File tree

7 files changed

+233
-14
lines changed

7 files changed

+233
-14
lines changed

src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public DevCycleCloudClient(String sdkKey, DevCycleCloudOptions options) {
5151
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
5252

5353
this.openFeatureProvider = new DevCycleProvider(this);
54-
this.evalHooksRunner = new EvalHooksRunner();
54+
this.evalHooksRunner = new EvalHooksRunner(dvcOptions.getHooks());
5555
}
5656

5757
/**

src/main/java/com/devcycle/sdk/server/cloud/model/DevCycleCloudOptions.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import com.devcycle.sdk.server.common.api.IRestOptions;
44
import com.devcycle.sdk.server.common.logging.IDevCycleLogger;
5+
import com.devcycle.sdk.server.common.model.EvalHook;
56
import com.devcycle.sdk.server.common.model.IDevCycleOptions;
67
import lombok.Builder;
78
import lombok.Data;
89

10+
import java.util.ArrayList;
11+
import java.util.List;
12+
913
@Data
1014
@Builder
1115
public class DevCycleCloudOptions {
@@ -21,6 +25,9 @@ public class DevCycleCloudOptions {
2125
@Builder.Default
2226
private IRestOptions restOptions = null;
2327

28+
@Builder.Default
29+
private List<EvalHook> hooks = new ArrayList<>();
30+
2431
public static class DevCycleCloudOptionsBuilder implements IDevCycleOptions {
2532
}
2633
}

src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ public class EvalHooksRunner<T> {
1818
/**
1919
* Default constructor initializes an empty list of hooks.
2020
*/
21+
public EvalHooksRunner(List<EvalHook<T>> hooks) {
22+
if (hooks == null) {
23+
this.hooks = new ArrayList<>();
24+
} else {
25+
this.hooks = hooks;
26+
}
27+
}
28+
2129
public EvalHooksRunner() {
2230
this.hooks = new ArrayList<>();
2331
}

src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public DevCycleLocalClient(String sdkKey, DevCycleLocalOptions dvcOptions) {
6060
} catch (Exception e) {
6161
DevCycleLogger.error("Error creating event queue due to error: " + e.getMessage());
6262
}
63-
this.evalHooksRunner = new EvalHooksRunner();
63+
this.evalHooksRunner = new EvalHooksRunner(dvcOptions.getHooks());
6464
}
6565

6666
/**

src/main/java/com/devcycle/sdk/server/local/model/DevCycleLocalOptions.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import com.devcycle.sdk.server.common.api.IRestOptions;
44
import com.devcycle.sdk.server.common.logging.DevCycleLogger;
55
import com.devcycle.sdk.server.common.logging.IDevCycleLogger;
6+
import com.devcycle.sdk.server.common.model.EvalHook;
67
import com.devcycle.sdk.server.common.model.IDevCycleOptions;
78
import com.fasterxml.jackson.annotation.JsonIgnore;
89
import lombok.Builder;
910
import lombok.Data;
1011

12+
import java.util.ArrayList;
13+
import java.util.List;
14+
1115
@Data
1216
public class DevCycleLocalOptions implements IDevCycleOptions {
1317
private int configRequestTimeoutMs = 10000;
@@ -41,6 +45,8 @@ public class DevCycleLocalOptions implements IDevCycleOptions {
4145
private boolean disableCustomEventLogging = false;
4246
@JsonIgnore
4347
private IRestOptions restOptions = null;
48+
@JsonIgnore
49+
private List<EvalHook> hooks = new ArrayList<>();
4450

4551
@Builder()
4652
public DevCycleLocalOptions(
@@ -60,7 +66,8 @@ public DevCycleLocalOptions(
6066
IRestOptions restOptions,
6167
@Deprecated
6268
boolean enableBetaRealtimeUpdates,
63-
boolean disableRealtimeUpdates
69+
boolean disableRealtimeUpdates,
70+
List<EvalHook> hooks
6471
) {
6572
this.configRequestTimeoutMs = configRequestTimeoutMs > 0 ? configRequestTimeoutMs : this.configRequestTimeoutMs;
6673
this.configPollingIntervalMS = getConfigPollingIntervalMS(configPollingIntervalMs, configPollingIntervalMS);
@@ -76,6 +83,7 @@ public DevCycleLocalOptions(
7683
this.restOptions = restOptions;
7784
this.enableBetaRealtimeUpdates = enableBetaRealtimeUpdates;
7885
this.disableRealtimeUpdates = disableRealtimeUpdates;
86+
this.hooks = hooks;
7987

8088
if (this.flushEventQueueSize >= this.maxEventQueueSize) {
8189
DevCycleLogger.warning("flushEventQueueSize: " + this.flushEventQueueSize + " must be smaller than maxEventQueueSize: " + this.maxEventQueueSize);

src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.devcycle.sdk.server.common.model.*;
99
import com.devcycle.sdk.server.helpers.WhiteBox;
1010
import dev.openfeature.sdk.Hook;
11+
import net.bytebuddy.implementation.bytecode.Throw;
1112
import org.junit.Assert;
1213
import org.junit.Before;
1314
import org.junit.Test;
@@ -23,6 +24,7 @@
2324
import java.util.Map;
2425
import java.util.Optional;
2526
import java.util.UUID;
27+
import java.util.*;
2628

2729
import static org.mockito.Mockito.when;
2830

@@ -747,6 +749,96 @@ public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variab
747749
Assert.assertTrue(finallyCalled[0]);
748750
}
749751

752+
@Test
753+
public void client_withHooksInOptions_addsHooksToEvalRunner() {
754+
DevCycleUser user = DevCycleUser.builder()
755+
.userId("j_test")
756+
.build();
757+
758+
final boolean[] beforeCalled = {false, false};
759+
final boolean[] afterCalled = {false, false};
760+
final boolean[] finallyCalled = {false, false};
761+
final boolean[] errorCalled = {false, false};
762+
763+
List<EvalHook> hooks = new ArrayList<>();
764+
hooks.add(new EvalHook<String>() {
765+
@Override
766+
public Optional<HookContext<String>> before(HookContext<String> ctx) {
767+
beforeCalled[0] = true;
768+
return Optional.empty();
769+
}
770+
771+
@Override
772+
public void after(HookContext<String> ctx, Variable<String> variable) {
773+
afterCalled[0] = true;
774+
}
775+
776+
@Override
777+
public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variable) {
778+
finallyCalled[0] = true;
779+
}
780+
781+
@Override
782+
public void error(HookContext<String> ctx, Throwable e) {
783+
errorCalled[0] = true;
784+
}
785+
});
786+
hooks.add(new EvalHook<String>() {
787+
@Override
788+
public Optional<HookContext<String>> before(HookContext<String> ctx) {
789+
beforeCalled[1] = true;
790+
return Optional.empty();
791+
}
792+
793+
@Override
794+
public void after(HookContext<String> ctx, Variable<String> variable) {
795+
afterCalled[1] = true;
796+
}
797+
798+
@Override
799+
public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variable) {
800+
finallyCalled[1] = true;
801+
}
802+
803+
@Override
804+
public void error(HookContext<String> ctx, Throwable e) {
805+
errorCalled[1] = true;
806+
}
807+
});
808+
809+
DevCycleCloudOptions options = DevCycleCloudOptions.builder()
810+
.hooks(hooks)
811+
.build();
812+
813+
final String apiKey = String.format("server-%s", UUID.randomUUID());
814+
DevCycleCloudClient client = new DevCycleCloudClient(apiKey, options);
815+
WhiteBox.setInternalState(client, "api", apiInterface);
816+
817+
Variable<String> expected = Variable.<String>builder()
818+
.key("test-string")
819+
.value("test value")
820+
.type(Variable.TypeEnum.STRING)
821+
.isDefaulted(false)
822+
.defaultValue("default string")
823+
.build();
824+
825+
when(apiInterface.getVariableByKey(user, "test-string", false)).thenReturn(Calls.response(expected));
826+
827+
Variable<String> result = client.variable(user, "test-string", "default string");
828+
829+
Assert.assertEquals(expected, result);
830+
831+
Assert.assertTrue(beforeCalled[0]);
832+
Assert.assertTrue(afterCalled[0]);
833+
Assert.assertTrue(finallyCalled[0]);
834+
Assert.assertFalse(errorCalled[0]);
835+
836+
Assert.assertTrue(beforeCalled[1]);
837+
Assert.assertTrue(afterCalled[1]);
838+
Assert.assertTrue(finallyCalled[1]);
839+
Assert.assertFalse(errorCalled[1]);
840+
}
841+
750842
private void assertUserDefaultsCorrect(DevCycleUser user) {
751843
Assert.assertEquals("Java", user.getPlatform());
752844
Assert.assertEquals(PlatformData.SdkTypeEnum.SERVER, user.getSdkType());

src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.devcycle.sdk.server.local;
22

3+
import com.devcycle.sdk.server.cloud.api.DevCycleCloudClient;
4+
import com.devcycle.sdk.server.cloud.model.DevCycleCloudOptions;
35
import com.devcycle.sdk.server.common.api.IRestOptions;
46
import com.devcycle.sdk.server.common.exception.DevCycleException;
57
import com.devcycle.sdk.server.common.logging.IDevCycleLogger;
68
import com.devcycle.sdk.server.common.model.*;
79
import com.devcycle.sdk.server.helpers.LocalConfigServer;
810
import com.devcycle.sdk.server.helpers.TestDataFixtures;
11+
import com.devcycle.sdk.server.helpers.WhiteBox;
912
import com.devcycle.sdk.server.local.api.DevCycleLocalClient;
1013
import com.devcycle.sdk.server.local.model.DevCycleLocalOptions;
1114
import org.junit.AfterClass;
@@ -15,6 +18,7 @@
1518
import org.junit.After;
1619
import org.junit.runner.RunWith;
1720
import org.mockito.junit.MockitoJUnitRunner;
21+
import retrofit2.mock.Calls;
1822

1923
import javax.net.ssl.HostnameVerifier;
2024
import javax.net.ssl.SSLSocketFactory;
@@ -24,6 +28,9 @@
2428
import java.util.Map;
2529
import java.util.Optional;
2630
import java.util.UUID;
31+
import java.util.*;
32+
33+
import static org.mockito.Mockito.when;
2734

2835
@RunWith(MockitoJUnitRunner.class)
2936
public class DevCycleLocalClientTest {
@@ -90,17 +97,7 @@ public static void setup() throws Exception {
9097
client = createClient(TestDataFixtures.SmallConfig());
9198
}
9299

93-
private static DevCycleLocalClient createClient(String config) {
94-
localConfigServer.setConfigData(config);
95-
96-
DevCycleLocalOptions options = DevCycleLocalOptions.builder()
97-
.configCdnBaseUrl(localConfigServer.getHostRootURL())
98-
.configPollingIntervalMS(60000)
99-
.customLogger(testLoggingWrapper)
100-
.restOptions(restOptions)
101-
.build();
102-
103-
DevCycleLocalClient client = new DevCycleLocalClient(apiKey, options);
100+
private static void waitForClient(DevCycleLocalClient client) {
104101
try {
105102
int loops = 0;
106103
while (!client.isInitialized()) {
@@ -115,6 +112,21 @@ private static DevCycleLocalClient createClient(String config) {
115112
} catch (InterruptedException e) {
116113
// no-op
117114
}
115+
}
116+
117+
private static DevCycleLocalClient createClient(String config) {
118+
localConfigServer.setConfigData(config);
119+
120+
DevCycleLocalOptions options = DevCycleLocalOptions.builder()
121+
.configCdnBaseUrl(localConfigServer.getHostRootURL())
122+
.configPollingIntervalMS(60000)
123+
.customLogger(testLoggingWrapper)
124+
.restOptions(restOptions)
125+
.build();
126+
127+
DevCycleLocalClient client = new DevCycleLocalClient(apiKey, options);
128+
129+
waitForClient(client);
118130
return client;
119131
}
120132

@@ -844,6 +856,98 @@ public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variab
844856
Assert.assertTrue(finallyCalled[0]);
845857
}
846858

859+
@Test
860+
public void client_withHooksInOptions_addsHooksToEvalRunner() {
861+
DevCycleUser user = DevCycleUser.builder()
862+
.userId("j_test")
863+
.build();
864+
865+
final boolean[] beforeCalled = {false, false};
866+
final boolean[] afterCalled = {false, false};
867+
final boolean[] finallyCalled = {false, false};
868+
final boolean[] errorCalled = {false, false};
869+
870+
List<EvalHook> hooks = new ArrayList<>();
871+
hooks.add(new EvalHook<String>() {
872+
@Override
873+
public Optional<HookContext<String>> before(HookContext<String> ctx) {
874+
beforeCalled[0] = true;
875+
return Optional.empty();
876+
}
877+
878+
@Override
879+
public void after(HookContext<String> ctx, Variable<String> variable) {
880+
afterCalled[0] = true;
881+
}
882+
883+
@Override
884+
public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variable) {
885+
finallyCalled[0] = true;
886+
}
887+
888+
@Override
889+
public void error(HookContext<String> ctx, Throwable e) {
890+
errorCalled[0] = true;
891+
}
892+
});
893+
hooks.add(new EvalHook<String>() {
894+
@Override
895+
public Optional<HookContext<String>> before(HookContext<String> ctx) {
896+
beforeCalled[1] = true;
897+
return Optional.empty();
898+
}
899+
900+
@Override
901+
public void after(HookContext<String> ctx, Variable<String> variable) {
902+
afterCalled[1] = true;
903+
}
904+
905+
@Override
906+
public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variable) {
907+
finallyCalled[1] = true;
908+
}
909+
910+
@Override
911+
public void error(HookContext<String> ctx, Throwable e) {
912+
errorCalled[1] = true;
913+
}
914+
});
915+
916+
DevCycleLocalOptions options = DevCycleLocalOptions.builder()
917+
.configCdnBaseUrl(localConfigServer.getHostRootURL())
918+
.configPollingIntervalMS(60000)
919+
.customLogger(testLoggingWrapper)
920+
.restOptions(restOptions)
921+
.hooks(hooks)
922+
.build();
923+
localConfigServer.setConfigData(TestDataFixtures.SmallConfig());
924+
925+
DevCycleLocalClient client = new DevCycleLocalClient(apiKey, options);
926+
waitForClient(client);
927+
928+
Variable<String> expected = Variable.<String>builder()
929+
.key("string-var")
930+
.value("variationOn")
931+
.type(Variable.TypeEnum.STRING)
932+
.isDefaulted(false)
933+
.defaultValue("default string")
934+
.build();
935+
936+
Variable<String> result = client.variable(user, "string-var", "default string");
937+
938+
Assert.assertEquals(expected, result);
939+
940+
Assert.assertTrue(beforeCalled[0]);
941+
Assert.assertTrue(afterCalled[0]);
942+
Assert.assertTrue(finallyCalled[0]);
943+
Assert.assertFalse(errorCalled[0]);
944+
945+
Assert.assertTrue(beforeCalled[1]);
946+
Assert.assertTrue(afterCalled[1]);
947+
Assert.assertTrue(finallyCalled[1]);
948+
Assert.assertFalse(errorCalled[1]);
949+
}
950+
847951
@After
848952
public void clearHooks() {
849953
client.clearHooks();

0 commit comments

Comments
 (0)