diff --git a/build.gradle b/build.gradle index b51359d..4730d00 100644 --- a/build.gradle +++ b/build.gradle @@ -107,12 +107,14 @@ nexusPublishing { dependencies { implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '[7.1.0, 8.0.0)' - implementation 'dev.openfeature:sdk:[1.7.0,2.0.0)' + implementation 'dev.openfeature:sdk:[1.14.0,2.0.0)' // Use JUnit test framework testImplementation(platform('org.junit:junit-bom:5.10.0')) testImplementation('org.junit.jupiter:junit-jupiter') testImplementation "org.mockito:mockito-core:3.+" + + testImplementation 'org.awaitility:awaitility:4.2.0' } test { diff --git a/src/main/java/com/launchdarkly/openfeature/serverprovider/Provider.java b/src/main/java/com/launchdarkly/openfeature/serverprovider/Provider.java index ca58439..acb8e15 100644 --- a/src/main/java/com/launchdarkly/openfeature/serverprovider/Provider.java +++ b/src/main/java/com/launchdarkly/openfeature/serverprovider/Provider.java @@ -222,6 +222,38 @@ public void shutdown() { } } + @Override + public void track(String eventName, EvaluationContext ctx, TrackingEventDetails details) { + if (ctx == null) { + logger.info( + "The 'track' method was called with a null EvaluationContext. " + + "No 'track' event will be sent to LaunchDarkly. " + + "The LaunchDarkly SDK requires a non-null EvaluationContext."); + return; + } + + if (details != null) { + Double metricValue = null; + if (details.getValue().isPresent()) { + metricValue = (Double) details.getValue().get(); + } + // Convert the Structure portion of the TrackingEventDetails into a key value + // map. + // This will not put the metricValue extracted above into the map. + LDValue data = valueConverter.toLdValue(new Value(details)); + + if (metricValue != null) { + client.trackMetric(eventName, evaluationContextConverter.toLdContext(ctx), data, metricValue); + } else if (!data.isNull() && data.size() > 0) { + client.trackData(eventName, evaluationContextConverter.toLdContext(ctx), data); + } else { + client.track(eventName, evaluationContextConverter.toLdContext(ctx)); + } + } else { + client.track(eventName, evaluationContextConverter.toLdContext(ctx)); + } + } + /** * Get the LaunchDarkly client associated with this provider. *

diff --git a/src/main/java/com/launchdarkly/openfeature/serverprovider/Version.java b/src/main/java/com/launchdarkly/openfeature/serverprovider/Version.java index fc7ca1a..ffd52fc 100644 --- a/src/main/java/com/launchdarkly/openfeature/serverprovider/Version.java +++ b/src/main/java/com/launchdarkly/openfeature/serverprovider/Version.java @@ -4,5 +4,5 @@ abstract class Version { private Version() {} // This constant is updated automatically by our Gradle script during a release, if the project version has changed - static final String SDK_VERSION = "0.2.0"; + static final String SDK_VERSION = "1.0.1"; } diff --git a/src/test/java/com/launchdarkly/openfeature/serverprovider/ProviderTest.java b/src/test/java/com/launchdarkly/openfeature/serverprovider/ProviderTest.java index 757a4c5..765078a 100644 --- a/src/test/java/com/launchdarkly/openfeature/serverprovider/ProviderTest.java +++ b/src/test/java/com/launchdarkly/openfeature/serverprovider/ProviderTest.java @@ -9,6 +9,9 @@ import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; + +import org.awaitility.Awaitility; + import static org.junit.jupiter.api.Assertions.*; public class ProviderTest { @@ -31,19 +34,24 @@ public void itCanDoABooleanEvaluation() { EvaluationContext evaluationContext = new ImmutableContext("user-key"); when(mockedLdClient.boolVariationDetail("the-key", LDContext.create("user-key"), false)) - .thenReturn(EvaluationDetail.fromValue(true, 12, EvaluationReason.fallthrough())); + .thenReturn(EvaluationDetail.fromValue(true, 12, EvaluationReason.fallthrough())); OpenFeatureAPI.getInstance().setProvider(ldProvider); + Awaitility.await().forever().until(() -> OpenFeatureAPI + .getInstance() + .getClient() + .getBooleanValue("the-key", false, evaluationContext)); + assertTrue(OpenFeatureAPI - .getInstance() - .getClient() - .getBooleanValue("the-key", false, evaluationContext)); + .getInstance() + .getClient() + .getBooleanValue("the-key", false, evaluationContext)); FlagEvaluationDetails detailed = OpenFeatureAPI - .getInstance() - .getClient() - .getBooleanDetails("the-key", false, evaluationContext); + .getInstance() + .getClient() + .getBooleanDetails("the-key", false, evaluationContext); assertEquals(true, detailed.getValue()); assertEquals("12", detailed.getVariant()); @@ -55,20 +63,25 @@ public void itCanDoAStringEvaluation() { EvaluationContext evaluationContext = new ImmutableContext("user-key"); when(mockedLdClient.stringVariationDetail("the-key", LDContext.create("user-key"), "default")) - .thenReturn(EvaluationDetail - .fromValue("evaluated", 17, EvaluationReason.off())); + .thenReturn(EvaluationDetail + .fromValue("evaluated", 17, EvaluationReason.off())); OpenFeatureAPI.getInstance().setProvider(ldProvider); + Awaitility.await().forever().until(() -> OpenFeatureAPI + .getInstance() + .getClient() + .getStringValue("the-key", "default", evaluationContext).equals("evaluated")); + assertEquals("evaluated", OpenFeatureAPI - .getInstance() - .getClient() - .getStringValue("the-key", "default", evaluationContext)); + .getInstance() + .getClient() + .getStringValue("the-key", "default", evaluationContext)); FlagEvaluationDetails detailed = OpenFeatureAPI - .getInstance() - .getClient() - .getStringDetails("the-key", "default", evaluationContext); + .getInstance() + .getClient() + .getStringDetails("the-key", "default", evaluationContext); assertEquals("evaluated", detailed.getValue()); assertEquals("17", detailed.getVariant()); @@ -80,18 +93,24 @@ public void itCanDoADoubleEvaluation() { EvaluationContext evaluationContext = new ImmutableContext("user-key"); when(mockedLdClient.doubleVariationDetail("the-key", LDContext.create("user-key"), 0.0)) - .thenReturn(EvaluationDetail.fromValue(1.0, 42, EvaluationReason.targetMatch())); + .thenReturn(EvaluationDetail.fromValue(1.0, 42, EvaluationReason.targetMatch())); OpenFeatureAPI.getInstance().setProvider(ldProvider); + + Awaitility.await().forever().until(() -> OpenFeatureAPI + .getInstance() + .getClient() + .getDoubleValue("the-key", 0.0, evaluationContext) != 0.0); + assertEquals(1.0, OpenFeatureAPI - .getInstance() - .getClient() - .getDoubleValue("the-key", 0.0, evaluationContext), 0.00001); + .getInstance() + .getClient() + .getDoubleValue("the-key", 0.0, evaluationContext), 0.00001); FlagEvaluationDetails detailed = OpenFeatureAPI - .getInstance() - .getClient() - .getDoubleDetails("the-key", 0.0, evaluationContext); + .getInstance() + .getClient() + .getDoubleDetails("the-key", 0.0, evaluationContext); assertEquals(1.0, detailed.getValue(), 0.00001); assertEquals("42", detailed.getVariant()); @@ -122,8 +141,82 @@ public void itCanDoAValueEvaluation() { .getObjectDetails("the-key", new Value(), evaluationContext); assertEquals("aValue", detailed.getValue().asStructure().getValue("aKey").asString()); - assertEquals("84", detailed.getVariant()); assertEquals("TARGETING_MATCH", detailed.getReason()); } + + @Test + public void itCanTrackNoTrackingEventDetails() { + EvaluationContext evaluationContext = new ImmutableContext("user-key"); + EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null); + + OpenFeatureAPI.getInstance().setProvider(ldProvider); + + OpenFeatureAPI + .getInstance() + .getClient().track("metric-key", evaluationContext); + + verify(mockedLdClient).track( + "metric-key", + evaluationContextConverter.toLdContext(evaluationContext)); + } + + @Test + public void itCanTrackEmptyTrackingEventDetails() { + EvaluationContext evaluationContext = new ImmutableContext("user-key"); + EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null); + + TrackingEventDetails trackingEventDetails = new MutableTrackingEventDetails(null); + + OpenFeatureAPI.getInstance().setProvider(ldProvider); + + OpenFeatureAPI + .getInstance() + .getClient().track("metric-key", evaluationContext, trackingEventDetails); + + verify(mockedLdClient).track( + "metric-key", + evaluationContextConverter.toLdContext(evaluationContext)); + } + + @Test + public void itCanTrackTrackingEventDetailsWithNoValue() { + EvaluationContext evaluationContext = new ImmutableContext("user-key"); + EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null); + ValueConverter valueConverter = new ValueConverter(null); + + TrackingEventDetails trackingEventDetails = new MutableTrackingEventDetails(null).add("currency", "USD"); + + OpenFeatureAPI.getInstance().setProvider(ldProvider); + + OpenFeatureAPI + .getInstance() + .getClient().track("metric-key", evaluationContext, trackingEventDetails); + + verify(mockedLdClient).trackData( + "metric-key", + evaluationContextConverter.toLdContext(evaluationContext), + valueConverter.toLdValue(new Value(trackingEventDetails))); + } + + @Test + public void itCanTrackFullTrackingEventDetails() { + EvaluationContext evaluationContext = new ImmutableContext("user-key"); + EvaluationContextConverter evaluationContextConverter = new EvaluationContextConverter(null); + ValueConverter valueConverter = new ValueConverter(null); + + TrackingEventDetails trackingEventDetails = new MutableTrackingEventDetails(99.77).add("currency", "USD"); + + OpenFeatureAPI.getInstance().setProvider(ldProvider); + + OpenFeatureAPI + .getInstance() + .getClient().track("metric-key", evaluationContext, trackingEventDetails); + + verify(mockedLdClient).trackMetric( + "metric-key", + evaluationContextConverter.toLdContext(evaluationContext), + valueConverter.toLdValue(new Value(trackingEventDetails)), + 99.77); + } }