From 29688c77e4a755125f31b911efed6eee3a589b06 Mon Sep 17 00:00:00 2001 From: Kaushal Kapasi Date: Wed, 6 Aug 2025 17:20:14 -0400 Subject: [PATCH 1/2] chore: refactor imports, add builder.default to platformData sdkPlatform attribute --- .../devcycle/sdk/server/common/model/EvalHooksRunner.java | 8 ++++---- .../devcycle/sdk/server/common/model/PlatformData.java | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java b/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java index d7342782..a0febf84 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java @@ -1,13 +1,13 @@ package com.devcycle.sdk.server.common.model; -import com.devcycle.sdk.server.common.exception.AfterHookError; -import com.devcycle.sdk.server.common.exception.BeforeHookError; -import com.devcycle.sdk.server.common.logging.DevCycleLogger; - import java.util.ArrayList; import java.util.List; import java.util.Optional; +import com.devcycle.sdk.server.common.exception.AfterHookError; +import com.devcycle.sdk.server.common.exception.BeforeHookError; +import com.devcycle.sdk.server.common.logging.DevCycleLogger; + /** * A class that manages evaluation hooks for the DevCycle SDK. * Provides functionality to add and clear hooks, storing them in an array. diff --git a/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java b/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java index 3c2e4214..8930cee4 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java @@ -1,18 +1,19 @@ package com.devcycle.sdk.server.common.model; +import java.net.InetAddress; +import java.net.UnknownHostException; + import com.devcycle.sdk.server.common.logging.DevCycleLogger; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; -import java.net.InetAddress; -import java.net.UnknownHostException; - @Data @Builder @JsonInclude(JsonInclude.Include.NON_NULL) @@ -34,6 +35,7 @@ public class PlatformData { private String sdkVersion = "2.8.1"; @Schema(description = "DevCycle SDK Platform") + @Builder.Default private String sdkPlatform = null; @Schema(description = "Hostname where the SDK is running") From 0a34d7f076484ad05eb363352c04a8f2e0a036b1 Mon Sep 17 00:00:00 2001 From: Kaushal Kapasi Date: Thu, 7 Aug 2025 15:50:03 -0400 Subject: [PATCH 2/2] fix: update hooks to have VariableMetadata passed to after and onFinally hooks to add variable specific metadata --- .../server/cloud/api/DevCycleCloudClient.java | 4 +- .../sdk/server/common/model/EvalHook.java | 6 +- .../server/common/model/EvalHooksRunner.java | 11 +- .../server/local/api/DevCycleLocalClient.java | 7 +- .../server/local/model/VariableMetadata.java | 13 ++ .../server/cloud/DevCycleCloudClientTest.java | 63 +++++----- .../sdk/server/cloud/EvalHooksRunnerTest.java | 117 +++++++++++++++--- .../server/local/DevCycleLocalClientTest.java | 65 +++++----- 8 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/devcycle/sdk/server/local/model/VariableMetadata.java diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java index 6ae551e8..affc0d61 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java @@ -150,7 +150,7 @@ public Variable variable(DevCycleUser user, String key, T defaultValue) { } variable.setIsDefaulted(false); - evalHooksRunner.executeAfter(reversedHooks, context, variable); + evalHooksRunner.executeAfter(reversedHooks, context, variable, null); } catch (Throwable exception) { if (!(exception instanceof BeforeHookError || exception instanceof AfterHookError)) { variable = (Variable) Variable.builder() @@ -170,7 +170,7 @@ public Variable variable(DevCycleUser user, String key, T defaultValue) { evalHooksRunner.executeError(reversedHooks, context, exception); } finally { - evalHooksRunner.executeFinally(reversedHooks, context, Optional.ofNullable(variable)); + evalHooksRunner.executeFinally(reversedHooks, context, Optional.ofNullable(variable), null); } return variable; } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/EvalHook.java b/src/main/java/com/devcycle/sdk/server/common/model/EvalHook.java index 066a94b7..7f821eea 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/EvalHook.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/EvalHook.java @@ -2,12 +2,14 @@ import java.util.Optional; +import com.devcycle.sdk.server.local.model.VariableMetadata; + public interface EvalHook { default Optional> before(HookContext ctx) { return Optional.empty(); } - default void after(HookContext ctx, Variable variable) {} + default void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) {} default void error(HookContext ctx, Throwable e) {} - default void onFinally(HookContext ctx, Optional> variable) {} + default void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) {} } \ No newline at end of file diff --git a/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java b/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java index a0febf84..a5385538 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/EvalHooksRunner.java @@ -7,6 +7,7 @@ import com.devcycle.sdk.server.common.exception.AfterHookError; import com.devcycle.sdk.server.common.exception.BeforeHookError; import com.devcycle.sdk.server.common.logging.DevCycleLogger; +import com.devcycle.sdk.server.local.model.VariableMetadata; /** * A class that manages evaluation hooks for the DevCycle SDK. @@ -81,16 +82,16 @@ public HookContext executeBefore(ArrayList> hooks, HookContex * @param variable The variable result to pass to the hooks * @param The type of the variable value */ - public void executeAfter(ArrayList> hooks, HookContext context, Variable variable) { + public void executeAfter(ArrayList> hooks, HookContext context, Variable variable, VariableMetadata variableMetadata) { for (EvalHook hook : hooks) { try { - hook.after(context, variable); + hook.after(context, variable, variableMetadata); } catch (Exception e) { throw new AfterHookError("After hook failed", e); } } } - + /** * Runs all error hooks in reverse order. * @@ -115,10 +116,10 @@ public void executeError(ArrayList> hooks, HookContext context, T * @param context The context to pass to the hooks * @param variable The variable result to pass to the hooks (may be null) */ - public void executeFinally(ArrayList> hooks, HookContext context, Optional> variable) { + public void executeFinally(ArrayList> hooks, HookContext context, Optional> variable, VariableMetadata variableMetadata) { for (EvalHook hook : hooks) { try { - hook.onFinally(context, variable); + hook.onFinally(context, variable, variableMetadata); } catch (Exception e) { // Log finally hook error but don't throw DevCycleLogger.error("Finally hook failed: " + e.getMessage(), e); diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java index 377bcd71..3681347a 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java @@ -27,6 +27,7 @@ import com.devcycle.sdk.server.local.model.BucketedUserConfig; import com.devcycle.sdk.server.local.model.ConfigMetadata; import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; +import com.devcycle.sdk.server.local.model.VariableMetadata; import com.devcycle.sdk.server.local.protobuf.SDKVariable_PB; import com.devcycle.sdk.server.local.protobuf.VariableForUserParams_PB; import com.devcycle.sdk.server.local.protobuf.VariableType_PB; @@ -181,6 +182,7 @@ public Variable variable(DevCycleUser user, String key, T defaultValue) { HookContext hookContext = new HookContext(user, key, defaultValue, getMetadata()); Variable variable = null; + VariableMetadata variableMetadata = null; ArrayList> hooks = new ArrayList>(evalHooksRunner.getHooks()); ArrayList> reversedHooks = new ArrayList>(evalHooksRunner.getHooks()); Collections.reverse(reversedHooks); @@ -207,12 +209,13 @@ public Variable variable(DevCycleUser user, String key, T defaultValue) { variable.setEval(EvalReason.defaultReason(EvalReason.DefaultReasonDetailsEnum.VARIABLE_TYPE_MISMATCH)); } else { variable = ProtobufUtils.createVariable(sdkVariable, defaultValue); + variableMetadata = new VariableMetadata(sdkVariable.getFeature().getValue()); } } if (beforeError != null) { throw beforeError; } - evalHooksRunner.executeAfter(reversedHooks, hookContext, variable); + evalHooksRunner.executeAfter(reversedHooks, hookContext, variable, variableMetadata); } catch (Throwable e) { if (!(e instanceof BeforeHookError)) { DevCycleLogger.error("Unable to evaluate Variable " + key + " due to error: " + e, e); @@ -225,7 +228,7 @@ public Variable variable(DevCycleUser user, String key, T defaultValue) { variable = defaultVariable; variable.setEval(EvalReason.defaultReason(EvalReason.DefaultReasonDetailsEnum.USER_NOT_TARGETED)); } - evalHooksRunner.executeFinally(reversedHooks, hookContext, Optional.of(variable)); + evalHooksRunner.executeFinally(reversedHooks, hookContext, Optional.ofNullable(variable), variableMetadata); } return variable; } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/VariableMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/VariableMetadata.java new file mode 100644 index 00000000..3b53c7fc --- /dev/null +++ b/src/main/java/com/devcycle/sdk/server/local/model/VariableMetadata.java @@ -0,0 +1,13 @@ +package com.devcycle.sdk.server.local.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class VariableMetadata { + + public final String featureId; + +} diff --git a/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java b/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java index 7434d01d..563e5d1c 100755 --- a/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java @@ -35,6 +35,7 @@ import com.devcycle.sdk.server.common.model.PlatformData; import com.devcycle.sdk.server.common.model.Variable; import com.devcycle.sdk.server.helpers.WhiteBox; +import com.devcycle.sdk.server.local.model.VariableMetadata; import retrofit2.mock.Calls; @@ -271,13 +272,13 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; Assert.assertTrue(beforeCalled[0]); } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; Assert.assertTrue(afterCalled[0]); } @@ -311,7 +312,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -321,7 +322,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -354,7 +355,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -364,7 +365,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -407,7 +408,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; throw new RuntimeException("Test after hook error"); } @@ -418,7 +419,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -461,7 +462,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -471,7 +472,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; throw new RuntimeException("Test finally hook error"); } @@ -514,7 +515,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -525,7 +526,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -573,7 +574,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { hook1AfterCalled[0] = true; } @@ -583,7 +584,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { hook1FinallyCalled[0] = true; } }); @@ -597,7 +598,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { hook2AfterCalled[0] = true; throw new RuntimeException("Test hook2 after error"); } @@ -608,7 +609,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { hook2FinallyCalled[0] = true; } }); @@ -659,7 +660,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -670,7 +671,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -713,7 +714,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -723,7 +724,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; throw new RuntimeException("Test finally hook error after previous error"); } @@ -767,7 +768,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -778,7 +779,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -822,12 +823,12 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } @@ -844,12 +845,12 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[1] = true; } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[1] = true; } @@ -908,7 +909,7 @@ public void variable_withEvalHooks_metadataIsNullInCloudClient() { api.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { // Cloud client should have null metadata since it doesn't manage local config Assert.assertNull("Cloud client metadata should be null", ctx.getMetadata()); metadataChecked[0] = true; @@ -947,12 +948,12 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { metadataWasNull[1] = (ctx.getMetadata() == null); } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { metadataWasNull[2] = (ctx.getMetadata() == null); } }); @@ -1024,7 +1025,7 @@ public void variable_withMultipleHooks_allReceiveNullMetadata() { // First hook api.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { Assert.assertNull("First hook should receive null metadata in cloud client", ctx.getMetadata()); metadataChecked[0] = true; } @@ -1033,7 +1034,7 @@ public void after(HookContext ctx, Variable variable) { // Second hook api.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { Assert.assertNull("Second hook should receive null metadata in cloud client", ctx.getMetadata()); metadataChecked[1] = true; } diff --git a/src/test/java/com/devcycle/sdk/server/cloud/EvalHooksRunnerTest.java b/src/test/java/com/devcycle/sdk/server/cloud/EvalHooksRunnerTest.java index 503f7f10..14dae620 100644 --- a/src/test/java/com/devcycle/sdk/server/cloud/EvalHooksRunnerTest.java +++ b/src/test/java/com/devcycle/sdk/server/cloud/EvalHooksRunnerTest.java @@ -1,20 +1,27 @@ package com.devcycle.sdk.server.cloud; -import com.devcycle.sdk.server.common.exception.AfterHookError; -import com.devcycle.sdk.server.common.exception.BeforeHookError; -import com.devcycle.sdk.server.common.model.*; +import java.util.ArrayList; +import java.util.Optional; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; -import java.util.Optional; +import com.devcycle.sdk.server.common.exception.AfterHookError; +import com.devcycle.sdk.server.common.exception.BeforeHookError; +import com.devcycle.sdk.server.common.model.DevCycleUser; +import com.devcycle.sdk.server.common.model.EvalHook; +import com.devcycle.sdk.server.common.model.EvalHooksRunner; +import com.devcycle.sdk.server.common.model.HookContext; +import com.devcycle.sdk.server.common.model.Variable; +import com.devcycle.sdk.server.local.model.VariableMetadata; public class EvalHooksRunnerTest { private EvalHooksRunner hookRunner; private DevCycleUser testUser; private Variable testVariable; + private VariableMetadata testVariableMetadata; @Before public void setup() { @@ -25,6 +32,7 @@ public void setup() { .value(true) .type(Variable.TypeEnum.BOOLEAN) .build(); + testVariableMetadata = new VariableMetadata("test-feature-id"); } @Test @@ -69,13 +77,32 @@ public void testAfterHook() { ArrayList> hooks = new ArrayList<>(); hooks.add(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { hookCalled[0] = true; Assert.assertEquals("test-var", variable.getKey()); } }); - hookRunner.executeAfter(hooks, context, testVariable); + hookRunner.executeAfter(hooks, context, testVariable, null); + Assert.assertTrue(hookCalled[0]); + } + + @Test + public void testAfterHookWithMetadata() { + final boolean[] hookCalled = {false}; + HookContext context = new HookContext<>(testUser, "test-key", false, null); + + ArrayList> hooks = new ArrayList<>(); + hooks.add(new EvalHook() { + @Override + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { + hookCalled[0] = true; + Assert.assertEquals("test-var", variable.getKey()); + Assert.assertEquals("test-feature-id", variableMetadata.featureId); + } + }); + + hookRunner.executeAfter(hooks, context, testVariable, testVariableMetadata); Assert.assertTrue(hookCalled[0]); } @@ -86,12 +113,12 @@ public void testAfterHookError() { ArrayList> hooks = new ArrayList<>(); hooks.add(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { throw new RuntimeException("Test error"); } }); - hookRunner.executeAfter(hooks, context, testVariable); + hookRunner.executeAfter(hooks, context, testVariable, null); } @Test @@ -121,12 +148,29 @@ public void testFinallyHook() { ArrayList> hooks = new ArrayList<>(); hooks.add(new EvalHook() { @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { hookCalled[0] = true; } }); - hookRunner.executeFinally(hooks, context, Optional.ofNullable(testVariable)); + hookRunner.executeFinally(hooks, context, Optional.ofNullable(testVariable), null); + Assert.assertTrue(hookCalled[0]); + } + + @Test + public void testFinallyHookWithMetadata() { + final boolean[] hookCalled = {false}; + HookContext context = new HookContext<>(testUser, "test-key", false, null); + + ArrayList> hooks = new ArrayList<>(); + hooks.add(new EvalHook() { + @Override + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { + hookCalled[0] = true; + } + }); + + hookRunner.executeFinally(hooks, context, Optional.ofNullable(testVariable), testVariableMetadata); Assert.assertTrue(hookCalled[0]); } @@ -138,14 +182,14 @@ public void testClearHooks() { ArrayList> hooks = new ArrayList<>(); hooks.add(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { hookCalled[0] = true; } }); // Test that empty hooks array doesn't call any hooks ArrayList> emptyHooks = new ArrayList<>(); - hookRunner.executeAfter(emptyHooks, context, testVariable); + hookRunner.executeAfter(emptyHooks, context, testVariable, null); Assert.assertFalse(hookCalled[0]); } @@ -163,24 +207,63 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { capturedMetadata[1] = ctx.getMetadata(); } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { capturedMetadata[2] = ctx.getMetadata(); } }); // Execute hooks and verify metadata is consistently passed through HookContext beforeResult = hookRunner.executeBefore(hooks, context); - hookRunner.executeAfter(hooks, beforeResult, testVariable); - hookRunner.executeFinally(hooks, beforeResult, Optional.of(testVariable)); + hookRunner.executeAfter(hooks, beforeResult, testVariable, null); + hookRunner.executeFinally(hooks, beforeResult, Optional.of(testVariable), null); // Verify metadata is consistently null (as passed in the context) Assert.assertNull("Before hook should receive null metadata", capturedMetadata[0]); Assert.assertNull("After hook should receive null metadata", capturedMetadata[1]); Assert.assertNull("Finally hook should receive null metadata", capturedMetadata[2]); } + + @Test + public void testMetadataPassedThroughHooksWithVariableMetadata() { + final Object[] capturedMetadata = {null, null, null, null, null}; // before, after, finally + HookContext context = new HookContext<>(testUser, "test-key", false, null); + + ArrayList> hooks = new ArrayList<>(); + hooks.add(new EvalHook() { + @Override + public Optional> before(HookContext ctx) { + capturedMetadata[0] = ctx.getMetadata(); + return Optional.empty(); + } + + @Override + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { + capturedMetadata[1] = ctx.getMetadata(); + capturedMetadata[2] = variableMetadata; + } + + @Override + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { + capturedMetadata[3] = ctx.getMetadata(); + capturedMetadata[4] = variableMetadata; + } + }); + + // Execute hooks and verify metadata is consistently passed through + HookContext beforeResult = hookRunner.executeBefore(hooks, context); + hookRunner.executeAfter(hooks, beforeResult, testVariable, testVariableMetadata); + hookRunner.executeFinally(hooks, beforeResult, Optional.of(testVariable), testVariableMetadata); + + // Verify metadata is consistently null (as passed in the context) + Assert.assertNull("Before hook should receive null metadata", capturedMetadata[0]); + Assert.assertNull("After hook should receive null metadata", capturedMetadata[1]); + Assert.assertEquals("After hook should receive variable metadata", testVariableMetadata, capturedMetadata[2]); + Assert.assertNull("Finally hook should receive null metadata", capturedMetadata[3]); + Assert.assertEquals("Finally hook should receive variable metadata", testVariableMetadata, capturedMetadata[4]); + } } diff --git a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java index 4b209d1a..204960a6 100644 --- a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java @@ -40,6 +40,7 @@ import com.devcycle.sdk.server.local.model.EnvironmentMetadata; import com.devcycle.sdk.server.local.model.Project; import com.devcycle.sdk.server.local.model.ProjectMetadata; +import com.devcycle.sdk.server.local.model.VariableMetadata; @RunWith(MockitoJUnitRunner.class) public class DevCycleLocalClientTest { @@ -458,13 +459,13 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; Assert.assertTrue(beforeCalled[0]); } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; Assert.assertTrue(afterCalled[0]); } @@ -495,7 +496,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -505,7 +506,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -538,7 +539,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; throw new RuntimeException("Test after hook error"); } @@ -549,7 +550,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -582,7 +583,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -592,7 +593,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; throw new RuntimeException("Test finally hook error"); } @@ -625,7 +626,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -636,7 +637,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -675,7 +676,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { hook1AfterCalled[0] = true; } @@ -685,7 +686,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { hook1FinallyCalled[0] = true; } }); @@ -699,7 +700,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { hook2AfterCalled[0] = true; throw new RuntimeException("Test hook2 after error"); } @@ -710,7 +711,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { hook2FinallyCalled[0] = true; } }); @@ -758,7 +759,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -770,7 +771,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -803,7 +804,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -815,7 +816,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -848,7 +849,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -858,7 +859,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; throw new RuntimeException("Test finally hook error after previous error"); } @@ -892,7 +893,7 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @@ -903,7 +904,7 @@ public void error(HookContext ctx, Throwable error) { } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } }); @@ -937,12 +938,12 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[0] = true; } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[0] = true; } @@ -959,12 +960,12 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { afterCalled[1] = true; } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { finallyCalled[1] = true; } @@ -1031,7 +1032,7 @@ public void variable_withEvalHooks_metadataIsAccessibleInAfterHook() throws DevC client.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { // Verify metadata is accessible and properly populated Assert.assertNotNull("Metadata should not be null", ctx.getMetadata()); @@ -1067,12 +1068,12 @@ public Optional> before(HookContext ctx) { } @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { capturedMetadata[1] = ctx.getMetadata(); } @Override - public void onFinally(HookContext ctx, Optional> variable) { + public void onFinally(HookContext ctx, Optional> variable, VariableMetadata variableMetadata) { capturedMetadata[2] = ctx.getMetadata(); } }); @@ -1132,7 +1133,7 @@ public void variable_withEvalHooks_metadataReflectsCurrentConfig() throws DevCyc client.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { capturedMetadata[0] = ctx.getMetadata(); } }); @@ -1162,7 +1163,7 @@ public void variable_withMultipleHooks_allReceiveMetadata() throws DevCycleExcep // First hook client.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { Assert.assertNotNull("First hook should receive metadata", ctx.getMetadata()); Assert.assertNotNull("First hook metadata should have project", ctx.getMetadata().project); metadataChecked[0] = true; @@ -1172,7 +1173,7 @@ public void after(HookContext ctx, Variable variable) { // Second hook client.addHook(new EvalHook() { @Override - public void after(HookContext ctx, Variable variable) { + public void after(HookContext ctx, Variable variable, VariableMetadata variableMetadata) { Assert.assertNotNull("Second hook should receive metadata", ctx.getMetadata()); Assert.assertNotNull("Second hook metadata should have environment", ctx.getMetadata().environment); metadataChecked[1] = true;