Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -138,11 +138,21 @@ 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");
}
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 +161,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", "provider-status", "reason-codes", "async", "immutability", "evaluation-options"})
public class GherkinSpecTest {}
9 changes: 9 additions & 0 deletions src/test/java/dev/openfeature/sdk/e2e/Utils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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 {
Expand All @@ -22,6 +25,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(new ObjectMapper().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 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_error_message_should_contain(String errorCode) {
assertThat(state.evaluation.getErrorMessage()).contains(errorCode);
}

@Then("the resolved metadata value \"{}\" with type \"{}\" should be \"{}\"")
public void theResolvedMetadataValueShouldBe(String key, String type, String value)
throws NoSuchFieldException, IllegalAccessException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import lombok.SneakyThrows;

@Deprecated
public class StepDefinitions {

private static Client client;
Expand Down
118 changes: 38 additions & 80 deletions src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package dev.openfeature.sdk.testutils;

import static dev.openfeature.sdk.Structure.mapToStructure;

import com.google.common.collect.ImmutableMap;
import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import dev.openfeature.sdk.ImmutableMetadata;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.providers.memory.ContextEvaluator;
import dev.openfeature.sdk.providers.memory.Flag;
import java.util.HashMap;
import dev.openfeature.sdk.testutils.jackson.ContextEvaluatorDeserializer;
import dev.openfeature.sdk.testutils.jackson.ImmutableMetadataDeserializer;
import dev.openfeature.sdk.testutils.jackson.InMemoryFlagMixin;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Map;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

/**
* Test flags utils.
*/
@Slf4j
@UtilityClass
public class TestFlagsUtils {

Expand All @@ -25,87 +33,37 @@ public class TestFlagsUtils {
public static final String WRONG_FLAG_KEY = "wrong-flag";
public static final String METADATA_FLAG_KEY = "metadata-flag";

private static Map<String, Flag<?>> flags;
/**
* Building flags for testing purposes.
*
* @return map of flags
*/
public static Map<String, Flag<?>> buildFlags() {
Map<String, Flag<?>> flags = new HashMap<>();
flags.put(
BOOLEAN_FLAG_KEY,
Flag.builder()
.variant("on", true)
.variant("off", false)
.defaultVariant("on")
.build());
flags.put(
STRING_FLAG_KEY,
Flag.builder()
.variant("greeting", "hi")
.variant("parting", "bye")
.defaultVariant("greeting")
.build());
flags.put(
INT_FLAG_KEY,
Flag.builder()
.variant("one", 1)
.variant("ten", 10)
.defaultVariant("ten")
.build());
flags.put(
FLOAT_FLAG_KEY,
Flag.builder()
.variant("tenth", 0.1)
.variant("half", 0.5)
.defaultVariant("half")
.build());
flags.put(
OBJECT_FLAG_KEY,
Flag.builder()
.variant("empty", new HashMap<>())
.variant(
"template",
new Value(mapToStructure(ImmutableMap.of(
"showImages", new Value(true),
"title", new Value("Check out these pics!"),
"imagesPerPage", new Value(100)))))
.defaultVariant("template")
.build());
flags.put(
CONTEXT_AWARE_FLAG_KEY,
Flag.<String>builder()
.variant("internal", "INTERNAL")
.variant("external", "EXTERNAL")
.defaultVariant("external")
.contextEvaluator((flag, evaluationContext) -> {
if (new Value(false).equals(evaluationContext.getValue("customer"))) {
return (String) flag.getVariants().get("internal");
} else {
return (String) flag.getVariants().get(flag.getDefaultVariant());
}
})
.build());
flags.put(
WRONG_FLAG_KEY,
Flag.builder()
.variant("one", "uno")
.variant("two", "dos")
.defaultVariant("one")
.build());
flags.put(
METADATA_FLAG_KEY,
Flag.builder()
.variant("on", true)
.variant("off", false)
.defaultVariant("on")
.flagMetadata(ImmutableMetadata.builder()
.addString("string", "1.0.2")
.addInteger("integer", 2)
.addBoolean("boolean", true)
.addDouble("float", 0.1d)
.build())
.build());
if (flags == null) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION.mappedFeature(), true);
objectMapper.addMixIn(Flag.class, InMemoryFlagMixin.class);
objectMapper.addMixIn(Flag.FlagBuilder.class, InMemoryFlagMixin.FlagBuilderMixin.class);

SimpleModule module = new SimpleModule();
module.addDeserializer(ImmutableMetadata.class, new ImmutableMetadataDeserializer());
module.addDeserializer(ContextEvaluator.class, new ContextEvaluatorDeserializer());
objectMapper.registerModule(module);

Map<String, Flag<?>> flagsJson;
try {
flagsJson = objectMapper.readValue(
Paths.get("spec/specification/assets/gherkin/test-flags.json")
.toFile(),
new TypeReference<>() {});

} catch (IOException e) {
throw new RuntimeException(e);
}
flags = Collections.unmodifiableMap(flagsJson);
}

return flags;
}
}
Loading
Loading