From 804d9bddc74b244b879bcc69cb371a887ca8bc8c Mon Sep 17 00:00:00 2001 From: "Gerges, Gabriella" Date: Wed, 11 Nov 2026 15:05:15 -0400 Subject: [PATCH 1/2] fix(openfeature): handle null details in DevCycleProvider.track() --- .../server/openfeature/DevCycleProvider.java | 6 ++++ .../openfeature/DevCycleProviderTest.java | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java b/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java index 07cb9d2..6f7ab2e 100644 --- a/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java +++ b/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java @@ -16,6 +16,7 @@ import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.ImmutableMetadata; import dev.openfeature.sdk.ImmutableMetadata.ImmutableMetadataBuilder; +import dev.openfeature.sdk.ImmutableTrackingEventDetails; import dev.openfeature.sdk.Metadata; import dev.openfeature.sdk.ProviderEvaluation; import dev.openfeature.sdk.Reason; @@ -214,6 +215,11 @@ public void track(String eventName, EvaluationContext context, TrackingEventDeta throw new ProviderNotReadyError("DevCycle client not initialized"); } + // OpenFeature passes null details when Client.track() is called without them + if (details == null) { + details = new ImmutableTrackingEventDetails(); + } + DevCycleUser user = DevCycleUser.fromEvaluationContext(context); try { BigDecimal eventValue = extractEventValue(details); diff --git a/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java index 51de0a3..e1b201f 100644 --- a/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java +++ b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java @@ -2,6 +2,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; @@ -13,14 +14,17 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.junit.MockitoJUnitRunner; import com.devcycle.sdk.server.common.api.IDevCycleClient; +import com.devcycle.sdk.server.common.model.DevCycleEvent; import com.devcycle.sdk.server.common.model.EvalReason; import com.devcycle.sdk.server.common.model.Variable; import dev.openfeature.sdk.ErrorCode; import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.ProviderEvaluation; import dev.openfeature.sdk.Reason; import dev.openfeature.sdk.Structure; @@ -32,6 +36,36 @@ @RunWith(MockitoJUnitRunner.class) public class DevCycleProviderTest { + @Test + public void testTrackWithNullDetails() throws Exception { + IDevCycleClient dvcClient = mock(IDevCycleClient.class); + when(dvcClient.isInitialized()).thenReturn(true); + + DevCycleProvider provider = new DevCycleProvider(dvcClient); + provider.track("checkout-completed", new ImmutableContext("user-1234"), null); + + ArgumentCaptor event = ArgumentCaptor.forClass(DevCycleEvent.class); + verify(dvcClient).track(any(), event.capture()); + + Assert.assertEquals("checkout-completed", event.getValue().getType()); + Assert.assertNull(event.getValue().getValue()); + Assert.assertEquals(new HashMap<>(), event.getValue().getMetaData()); + } + + @Test + public void testOpenFeatureClientTrackWithoutDetails() throws Exception { + IDevCycleClient dvcClient = mock(IDevCycleClient.class); + when(dvcClient.isInitialized()).thenReturn(true); + + String domain = "track-without-details"; + OpenFeatureAPI.getInstance().setProviderAndWait(domain, new DevCycleProvider(dvcClient)); + OpenFeatureAPI.getInstance().getClient(domain).track("checkout-completed", new ImmutableContext("user-1234")); + + ArgumentCaptor event = ArgumentCaptor.forClass(DevCycleEvent.class); + verify(dvcClient).track(any(), event.capture()); + Assert.assertEquals("checkout-completed", event.getValue().getType()); + } + @Test public void testResolveClientNotInitialized() { IDevCycleClient dvcClient = mock(IDevCycleClient.class); From b58d7cb0bb0d0c3b4b587bc5b7e409993528a98e Mon Sep 17 00:00:00 2001 From: "Gerges, Gabriella" Date: Wed, 11 Nov 2026 15:12:43 -0400 Subject: [PATCH 2/2] test(openfeature): merge null details tests into one --- .../openfeature/DevCycleProviderTest.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java index e1b201f..3687f78 100644 --- a/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java +++ b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderTest.java @@ -37,23 +37,7 @@ public class DevCycleProviderTest { @Test - public void testTrackWithNullDetails() throws Exception { - IDevCycleClient dvcClient = mock(IDevCycleClient.class); - when(dvcClient.isInitialized()).thenReturn(true); - - DevCycleProvider provider = new DevCycleProvider(dvcClient); - provider.track("checkout-completed", new ImmutableContext("user-1234"), null); - - ArgumentCaptor event = ArgumentCaptor.forClass(DevCycleEvent.class); - verify(dvcClient).track(any(), event.capture()); - - Assert.assertEquals("checkout-completed", event.getValue().getType()); - Assert.assertNull(event.getValue().getValue()); - Assert.assertEquals(new HashMap<>(), event.getValue().getMetaData()); - } - - @Test - public void testOpenFeatureClientTrackWithoutDetails() throws Exception { + public void testTrackWithoutDetails() throws Exception { IDevCycleClient dvcClient = mock(IDevCycleClient.class); when(dvcClient.isInitialized()).thenReturn(true); @@ -64,6 +48,8 @@ public void testOpenFeatureClientTrackWithoutDetails() throws Exception { ArgumentCaptor event = ArgumentCaptor.forClass(DevCycleEvent.class); verify(dvcClient).track(any(), event.capture()); Assert.assertEquals("checkout-completed", event.getValue().getType()); + Assert.assertNull(event.getValue().getValue()); + Assert.assertEquals(new HashMap<>(), event.getValue().getMetaData()); } @Test