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
4 changes: 4 additions & 0 deletions src/main/java/dev/openfeature/sdk/ImmutableMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ public boolean isEmpty() {
return metadata.isEmpty();
}

public boolean isNotEmpty() {
return !metadata.isEmpty();
}

/**
* Obtain a builder for {@link ImmutableMetadata}.
*/
Expand Down
10 changes: 6 additions & 4 deletions src/test/java/dev/openfeature/sdk/FlagMetadataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,25 @@ void notfound_error_validation() {
}

@Test
@DisplayName("isEmpty returns true iff the metadata is empty")
void isEmpty_returns_true_if_metadata_is_empty() {
@DisplayName("isEmpty and isNotEmpty return correctly when the metadata is empty")
void isEmpty_isNotEmpty_return_correctly_when_metadata_is_empty() {
// given
ImmutableMetadata flagMetadata = ImmutableMetadata.builder().build();

// then
assertTrue(flagMetadata.isEmpty());
assertFalse(flagMetadata.isNotEmpty());
}

@Test
@DisplayName("isEmpty returns false iff the metadata is not empty")
void isEmpty_returns_false_if_metadata_is_not_empty() {
@DisplayName("isEmpty and isNotEmpty return correctly when the metadata is not empty")
void isEmpty_isNotEmpty_return_correctly_when_metadata_is_not_empty() {
// given
ImmutableMetadata flagMetadata =
ImmutableMetadata.builder().addString("a", "b").build();

// then
assertFalse(flagMetadata.isEmpty());
assertTrue(flagMetadata.isNotEmpty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.openfeature.sdk.e2e;

import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.Metadata;
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.Value;
import lombok.Getter;

@Getter
public class ContextStoringProvider implements FeatureProvider {
private EvaluationContext evaluationContext;

@Override
public Metadata getMetadata() {
return () -> getClass().getSimpleName();
}

@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}

@Override
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}

@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}

@Override
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}

@Override
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
this.evaluationContext = ctx;
return null;
}
}
6 changes: 6 additions & 0 deletions src/test/java/dev/openfeature/sdk/e2e/State.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package dev.openfeature.sdk.e2e;

import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.MutableContext;
import java.util.List;

public class State {
public Client client;
public Flag flag;
public MutableContext context = new MutableContext();
public FlagEvaluationDetails evaluation;
public MockHook hook;
public FeatureProvider provider;
public EvaluationContext invocationContext;
public List<String> levels;
}
104 changes: 104 additions & 0 deletions src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package dev.openfeature.sdk.e2e.steps;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.Hook;
import dev.openfeature.sdk.HookContext;
import dev.openfeature.sdk.ImmutableContext;
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 io.cucumber.datatable.DataTable;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class ContextSteps {
private final State state;

public ContextSteps(State state) {
this.state = state;
}

@Given("a stable provider with retrievable context is registered")
public void setup() {
ContextStoringProvider provider = new ContextStoringProvider();
state.provider = provider;
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
state.client = OpenFeatureAPI.getInstance().getClient();
OpenFeatureAPI.getInstance().setTransactionContextPropagator(new ThreadLocalTransactionContextPropagator());
}

@When("A context entry with key {string} and value {string} is added to the {string} level")
public void aContextWithKeyAndValueIsAddedToTheLevel(String contextKey, String contextValue, String level) {
addContextEntry(contextKey, contextValue, level);
}

private void addContextEntry(String contextKey, String contextValue, String level) {
Map<String, Value> data = new HashMap<>();
data.put(contextKey, new Value(contextValue));
EvaluationContext context = new ImmutableContext(data);
if ("API".equals(level)) {
OpenFeatureAPI.getInstance().setEvaluationContext(context);
} else if ("Transaction".equals(level)) {
OpenFeatureAPI.getInstance().setTransactionContext(context);
} else if ("Client".equals(level)) {
state.client.setEvaluationContext(context);
} else if ("Invocation".equals(level)) {
state.invocationContext = context;
} else if ("Before Hooks".equals(level)) {
state.client.addHooks(new Hook() {
@Override
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
return Optional.of(context);
}
});
} else {
throw new IllegalArgumentException("Unknown level: " + level);
}
}

@When("Some flag was evaluated")
public void someFlagWasEvaluated() {
state.evaluation = state.client.getStringDetails("unused", "unused", state.invocationContext);
}

@Then("The merged context contains an entry with key {string} and value {string}")
public void theMergedContextContainsAnEntryWithKeyAndValue(String contextKey, String contextValue) {
assertInstanceOf(
ContextStoringProvider.class,
state.provider,
"In order to use this step, you need to set a ContextStoringProvider");
EvaluationContext ctx = ((ContextStoringProvider) state.provider).getEvaluationContext();
assertNotNull(ctx);
assertNotNull(ctx.getValue(contextKey));
assertNotNull(ctx.getValue(contextKey).asString());
assertEquals(contextValue, ctx.getValue(contextKey).asString());
}

@Given("A table with levels of increasing precedence")
public void aTableWithLevelsOfIncreasingPrecedence(DataTable levelsTable) {
state.levels = levelsTable.asList();
}

@And(
"Context entries for each level from API level down to the {string} level, with key {string} and value {string}")
public void contextEntriesForEachLevelFromAPILevelDownToTheLevelWithKeyAndValue(
String maxLevel, String key, String value) {
for (String level : state.levels) {
addContextEntry(key, value, level);
if (level.equals(maxLevel)) {
return;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static dev.openfeature.sdk.testutils.TestFlagsUtils.buildFlags;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EvaluationContext;
Expand Down Expand Up @@ -289,7 +288,7 @@ public void then_the_default_string_value_should_be_returned() {
@Then("the reason should indicate an error and the error code should indicate a missing flag with {string}")
public void the_reason_should_indicate_an_error_and_the_error_code_should_be_flag_not_found(String errorCode) {
assertEquals(Reason.ERROR.toString(), notFoundDetails.getReason());
assertTrue(notFoundDetails.getErrorCode().name().equals(errorCode));
assertEquals(errorCode, notFoundDetails.getErrorCode().name());
}

// type mismatch
Expand All @@ -309,6 +308,23 @@ public void then_the_default_integer_value_should_be_returned() {
@Then("the reason should indicate an error and the error code should indicate a type mismatch with {string}")
public void the_reason_should_indicate_an_error_and_the_error_code_should_be_type_mismatch(String errorCode) {
assertEquals(Reason.ERROR.toString(), typeErrorDetails.getReason());
assertTrue(typeErrorDetails.getErrorCode().name().equals(errorCode));
assertEquals(errorCode, typeErrorDetails.getErrorCode().name());
}

@SuppressWarnings("java:S2925")
@When("sleep for {int} milliseconds")
public void sleepForMilliseconds(int millis) {
long startTime = System.currentTimeMillis();
long endTime = startTime + millis;
long now;
while ((now = System.currentTimeMillis()) < endTime) {
long remainingTime = endTime - now;
try {
//noinspection BusyWait
Thread.sleep(remainingTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Loading