Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,31 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>dev.cel</groupId>
<artifactId>cel</artifactId>
<version>0.10.1</version>
<scope>test</scope>
</dependency>

</dependencies>

<dependencyManagement>
Expand All @@ -191,6 +216,14 @@
</dependency>
<!-- End mockito workaround-->

<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.16.1</version> <!-- Use the desired version -->
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-bom</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public class Flag<T> {
private String defaultVariant;
private ContextEvaluator<T> contextEvaluator;
private ImmutableMetadata flagMetadata;
private boolean disabled;
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,36 +97,37 @@ public void updateFlag(String flagKey, Flag<?> newFlag) {
@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(
String key, Boolean defaultValue, EvaluationContext evaluationContext) {
return getEvaluation(key, evaluationContext, Boolean.class);
return getEvaluation(key, defaultValue, evaluationContext, Boolean.class);
}

@Override
public ProviderEvaluation<String> getStringEvaluation(
String key, String defaultValue, EvaluationContext evaluationContext) {
return getEvaluation(key, evaluationContext, String.class);
return getEvaluation(key, defaultValue, evaluationContext, String.class);
}

@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(
String key, Integer defaultValue, EvaluationContext evaluationContext) {
return getEvaluation(key, evaluationContext, Integer.class);
return getEvaluation(key, defaultValue, evaluationContext, Integer.class);
}

@Override
public ProviderEvaluation<Double> getDoubleEvaluation(
String key, Double defaultValue, EvaluationContext evaluationContext) {
return getEvaluation(key, evaluationContext, Double.class);
return getEvaluation(key, defaultValue, evaluationContext, Double.class);
}

@SneakyThrows
@Override
public ProviderEvaluation<Value> getObjectEvaluation(
String key, Value defaultValue, EvaluationContext evaluationContext) {
return getEvaluation(key, evaluationContext, Value.class);
return getEvaluation(key, defaultValue, evaluationContext, Value.class);
}

private <T> ProviderEvaluation<T> getEvaluation(
String key, EvaluationContext evaluationContext, Class<?> expectedType) throws OpenFeatureError {
String key, T defaultValue, EvaluationContext evaluationContext, Class<?> expectedType)
throws OpenFeatureError {
if (!ProviderState.READY.equals(state)) {
if (ProviderState.NOT_READY.equals(state)) {
throw new ProviderNotReadyError("provider not yet initialized");
Expand All @@ -138,11 +139,28 @@ private <T> ProviderEvaluation<T> getEvaluation(
}
Flag<?> flag = flags.get(key);
if (flag == null) {
throw new FlagNotFoundError("flag " + key + "not found");
throw new FlagNotFoundError("flag " + key + " not found");
}
if (flag.isDisabled()) {
return ProviderEvaluation.<T>builder()
.reason(Reason.DISABLED.name())
.value(defaultValue)
.flagMetadata(flag.getFlagMetadata())
.build();
}
T value;
Reason reason = Reason.STATIC;
if (flag.getContextEvaluator() != null) {
value = (T) flag.getContextEvaluator().evaluate(flag, evaluationContext);
try {
value = (T) flag.getContextEvaluator().evaluate(flag, evaluationContext);
reason = Reason.TARGETING_MATCH;
} catch (Exception e) {
value = null;
}
if (value == null) {
value = (T) flag.getVariants().get(flag.getDefaultVariant());
reason = Reason.DEFAULT;
}
} else if (!expectedType.isInstance(flag.getVariants().get(flag.getDefaultVariant()))) {
throw new TypeMismatchError("flag " + key + "is not of expected type");
} else {
Expand All @@ -151,7 +169,7 @@ private <T> ProviderEvaluation<T> getEvaluation(
return ProviderEvaluation.<T>builder()
.value(value)
.variant(flag.getDefaultVariant())
.reason(Reason.STATIC.toString())
.reason(reason.toString())
.flagMetadata(flag.getFlagMetadata())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;

import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.ExcludeTags;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectDirectories;
import org.junit.platform.suite.api.Suite;
Expand All @@ -15,4 +16,5 @@
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.sdk.e2e.steps")
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
public class EvaluationTest {}
@ExcludeTags({"deprecated", "reason-codes-cached", "async", "immutability", "evaluation-options"})
public class GherkinSpecTest {}
11 changes: 11 additions & 0 deletions src/test/java/dev/openfeature/sdk/e2e/Utils.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package dev.openfeature.sdk.e2e;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.openfeature.sdk.Value;
import java.util.Objects;

public final class Utils {

public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private Utils() {}

public static Object convert(String value, String type) {
Expand All @@ -22,6 +27,12 @@ public static Object convert(String value, String type) {
return Double.parseDouble(value);
case "long":
return Long.parseLong(value);
case "object":
try {
return Value.objectToValue(OBJECT_MAPPER.readValue(value, Object.class));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
throw new RuntimeException("Unknown config type: " + type);
}
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
import dev.openfeature.sdk.Hook;
import dev.openfeature.sdk.HookContext;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.ImmutableStructure;
import dev.openfeature.sdk.MutableContext;
import dev.openfeature.sdk.OpenFeatureAPI;
import dev.openfeature.sdk.ThreadLocalTransactionContextPropagator;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.e2e.ContextStoringProvider;
import dev.openfeature.sdk.e2e.State;
import dev.openfeature.sdk.e2e.Utils;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
Expand Down Expand Up @@ -101,4 +104,29 @@ public void contextEntriesForEachLevelFromAPILevelDownToTheLevelWithKeyAndValue(
}
}
}

@Given("a context containing a key {string} with null value")
public void a_context_containing_a_key_with_null_value(String key) {
a_context_containing_a_key_with_type_and_with_value(key, "String", null);
}

@Given("a context containing a key {string}, with type {string} and with value {string}")
public void a_context_containing_a_key_with_type_and_with_value(String key, String type, String value) {
Map<String, Value> map = state.context.asMap();
map.put(key, Value.objectToValue(Utils.convert(value, type)));
state.context = new MutableContext(state.context.getTargetingKey(), map);
}

@Given("a context containing a targeting key with value {string}")
public void a_context_containing_a_targeting_key_with_value(String string) {
state.context.setTargetingKey(string);
}

@Given("a context containing a nested property with outer key {string} and inner key {string}, with value {string}")
public void a_context_containing_a_nested_property_with_outer_key_and_inner_key_with_value(
String outer, String inner, String value) {
Map<String, Value> innerMap = new HashMap<>();
innerMap.put(inner, new Value(value));
state.context.add(outer, new ImmutableStructure(innerMap));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.assertj.core.api.Assertions.assertThat;

import dev.openfeature.sdk.ErrorCode;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.ImmutableMetadata;
import dev.openfeature.sdk.Value;
Expand All @@ -23,7 +24,7 @@ public FlagStepDefinitions(State state) {
this.state = state;
}

@Given("a {}-flag with key {string} and a default value {string}")
@Given("a {}-flag with key {string} and a fallback value {string}")
public void givenAFlag(String type, String name, String defaultValue) {
state.flag = new Flag(type, name, Utils.convert(defaultValue, type));
}
Expand Down Expand Up @@ -60,7 +61,20 @@ public void the_flag_was_evaluated_with_details() {

@Then("the resolved details value should be {string}")
public void the_resolved_details_value_should_be(String value) {
assertThat(state.evaluation.getValue()).isEqualTo(Utils.convert(value, state.flag.type));
Object evaluationValue = state.evaluation.getValue();
if (state.flag.type.equalsIgnoreCase("object")) {
assertThat(((Value) evaluationValue).asStructure().asObjectMap())
.isEqualTo(((Value) Utils.convert(value, state.flag.type))
.asStructure()
.asObjectMap());
} else {
assertThat(evaluationValue).isEqualTo(Utils.convert(value, state.flag.type));
}
}

@Then("the flag key should be {string}")
public void the_flag_key_should_be(String key) {
assertThat(state.evaluation.getFlagKey()).isEqualTo(key);
}

@Then("the reason should be {string}")
Expand All @@ -73,6 +87,20 @@ public void the_variant_should_be(String variant) {
assertThat(state.evaluation.getVariant()).isEqualTo(variant);
}

@Then("the error-code should be {string}")
public void the_error_code_should_be(String errorCode) {
if (errorCode.isEmpty()) {
assertThat(state.evaluation.getErrorCode()).isNull();
} else {
assertThat(state.evaluation.getErrorCode()).isEqualTo(ErrorCode.valueOf(errorCode));
}
}

@Then("the error message should contain {string}")
public void the_error_message_should_contain(String messageSubstring) {
assertThat(state.evaluation.getErrorMessage()).contains(messageSubstring);
}

@Then("the resolved metadata value \"{}\" with type \"{}\" should be \"{}\"")
public void theResolvedMetadataValueShouldBe(String key, String type, String value)
throws NoSuchFieldException, IllegalAccessException {
Expand Down
Loading
Loading