chore: javadoc and tests for api, context (#942)

* chore: javadoc and tests for api, context

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

* fixup: lint

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
This commit is contained in:
Todd Baert 2024-06-14 10:14:49 -04:00 committed by GitHub
parent a0b1d25f49
commit 4126b511fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 34 deletions

View File

@ -48,30 +48,62 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
return SingletonHolder.INSTANCE;
}
/**
* Get metadata about the default provider.
*
* @return the provider metadata
*/
public Metadata getProviderMetadata() {
return getProvider().getMetadata();
}
/**
* Get metadata about a registered provider using the client name.
* An unbound or empty client name will return metadata from the default provider.
*
* @param domain an identifier which logically binds clients with providers
* @return the provider metadata
*/
public Metadata getProviderMetadata(String domain) {
return getProvider(domain).getMetadata();
}
/**
* {@inheritDoc}
* A factory function for creating new, OpenFeature clients.
* Clients can contain their own state (e.g. logger, hook, context).
* Multiple clients can be used to segment feature flag configuration.
* All un-named or unbound clients use the default provider.
*
* @return a new client instance
*/
public Client getClient() {
return getClient(null, null);
}
/**
* {@inheritDoc}
* A factory function for creating new domainless OpenFeature clients.
* Clients can contain their own state (e.g. logger, hook, context).
* Multiple clients can be used to segment feature flag configuration.
* If there is already a provider bound to this domain, this provider will be used.
* Otherwise, the default provider is used until a provider is assigned to that domain.
*
* @param domain an identifier which logically binds clients with providers
* @return a new client instance
*/
public Client getClient(String domain) {
return getClient(domain, null);
}
/**
* {@inheritDoc}
* A factory function for creating new domainless OpenFeature clients.
* Clients can contain their own state (e.g. logger, hook, context).
* Multiple clients can be used to segment feature flag configuration.
* If there is already a provider bound to this domain, this provider will be used.
* Otherwise, the default provider is used until a provider is assigned to that domain.
*
* @param domain a identifier which logically binds clients with providers
* @param version a version identifier
* @return a new client instance
*/
public Client getClient(String domain, String version) {
return new OpenFeatureClient(this,
@ -80,7 +112,10 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
}
/**
* {@inheritDoc}
* Sets the global evaluation context, which will be used for all evaluations.
*
* @param evaluationContext the context
* @return api instance
*/
public OpenFeatureAPI setEvaluationContext(EvaluationContext evaluationContext) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
@ -90,7 +125,9 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
}
/**
* {@inheritDoc}
* Gets the global evaluation context, which will be used for all evaluations.
*
* @return evaluation context
*/
public EvaluationContext getEvaluationContext() {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
@ -250,7 +287,10 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
}
/**
* {@inheritDoc}
* Adds hooks for globally, used for all evaluations.
* Hooks are run in the order they're added in the before stage. They are run in reverse order for all other stages.
*
* @param hooks The hook to add.
*/
public void addHooks(Hook... hooks) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
@ -259,7 +299,8 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
}
/**
* {@inheritDoc}
* Fetch the hooks associated to this client.
* @return A list of {@link Hook}s.
*/
public List<Hook> getHooks() {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
@ -268,7 +309,7 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
}
/**
* {@inheritDoc}
* Removes all hooks.
*/
public void clearHooks() {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {

View File

@ -7,8 +7,6 @@ class DoSomethingProvider implements FeatureProvider {
static final ImmutableMetadata DEFAULT_METADATA = ImmutableMetadata.builder().build();
private ImmutableMetadata flagMetadata;
private EvaluationContext savedContext;
public DoSomethingProvider() {
this.flagMetadata = DEFAULT_METADATA;
}
@ -17,10 +15,6 @@ class DoSomethingProvider implements FeatureProvider {
this.flagMetadata = flagMetadata;
}
EvaluationContext getMergedContext() {
return savedContext;
}
@Override
public Metadata getMetadata() {
return () -> name;
@ -28,7 +22,6 @@ class DoSomethingProvider implements FeatureProvider {
@Override
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
savedContext = ctx;
return ProviderEvaluation.<Boolean>builder()
.value(!defaultValue)
.flagMetadata(flagMetadata)
@ -45,7 +38,6 @@ class DoSomethingProvider implements FeatureProvider {
@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
savedContext = ctx;
return ProviderEvaluation.<Integer>builder()
.value(defaultValue * 100)
.flagMetadata(flagMetadata)
@ -54,7 +46,6 @@ class DoSomethingProvider implements FeatureProvider {
@Override
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
savedContext = ctx;
return ProviderEvaluation.<Double>builder()
.value(defaultValue * 100)
.flagMetadata(flagMetadata)
@ -63,7 +54,6 @@ class DoSomethingProvider implements FeatureProvider {
@Override
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext invocationContext) {
savedContext = invocationContext;
return ProviderEvaluation.<Value>builder()
.value(null)
.flagMetadata(flagMetadata)

View File

@ -11,7 +11,9 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ -307,11 +309,30 @@ class FlagEvaluationSpecTest implements HookFixtures {
assertNotNull(result.getFlagMetadata());
}
@Specification(number="3.2.1.1", text="The API, Client and invocation MUST have a method for supplying evaluation context.")
@Specification(number="3.2.2.1", text="The API MUST have a method for setting the global evaluation context.")
@Test void api_context() {
String contextKey = "some-key";
String contextValue = "some-value";
DoSomethingProvider provider = spy( new DoSomethingProvider());
FeatureProviderTestUtils.setFeatureProvider(provider);
Map<String, Value> attributes = new HashMap<>();
attributes.put(contextKey, new Value(contextValue));
EvaluationContext apiCtx = new ImmutableContext(attributes);
// set the global context
api.setEvaluationContext(apiCtx);
Client client = api.getClient();
client.getBooleanValue("any-flag", false);
// assert that the value from the global context was passed to the provider
verify(provider).getBooleanEvaluation(any(), any(), argThat((arg) -> arg.getValue(contextKey).asString().equals(contextValue)));
}
@Specification(number="3.2.1.1", text="The API, Client and invocation MUST have a method for supplying evaluation context.")
@Specification(number="3.2.3", text="Evaluation context MUST be merged in the order: API (global; lowest precedence) -> transaction -> client -> invocation -> before hooks (highest precedence), with duplicate values being overwritten.")
@Test void multi_layer_context_merges_correctly() {
DoSomethingProvider provider = new DoSomethingProvider();
DoSomethingProvider provider = spy(new DoSomethingProvider());
FeatureProviderTestUtils.setFeatureProvider(provider);
TransactionContextPropagator transactionContextPropagator = new ThreadLocalTransactionContextPropagator();
api.setTransactionContextPropagator(transactionContextPropagator);
@ -356,21 +377,21 @@ class FlagEvaluationSpecTest implements HookFixtures {
invocationAttributes.put("invocation", new Value("4"));
EvaluationContext invocationCtx = new ImmutableContext(invocationAttributes);
// dosomethingprovider inverts this value.
assertTrue(c.getBooleanValue("key", false, invocationCtx));
EvaluationContext merged = provider.getMergedContext();
assertEquals("1", merged.getValue("api").asString());
assertEquals("2", merged.getValue("transaction").asString());
assertEquals("3", merged.getValue("client").asString());
assertEquals("4", merged.getValue("invocation").asString());
assertEquals("2", merged.getValue("common1").asString(), "transaction merge is incorrect");
assertEquals("3", merged.getValue("common2").asString(), "api client merge is incorrect");
assertEquals("4", merged.getValue("common3").asString(), "invocation merge is incorrect");
assertEquals("3", merged.getValue("common4").asString(), "api client merge is incorrect");
assertEquals("4", merged.getValue("common5").asString(), "invocation merge is incorrect");
assertEquals("4", merged.getValue("common6").asString(), "invocation merge is incorrect");
c.getBooleanValue("key", false, invocationCtx);
// assert the connect overrides
verify(provider).getBooleanEvaluation(any(), any(), argThat((arg) -> {
return arg.getValue("api").asString().equals("1") &&
arg.getValue("transaction").asString().equals("2") &&
arg.getValue("client").asString().equals("3") &&
arg.getValue("invocation").asString().equals("4") &&
arg.getValue("common1").asString().equals("2") &&
arg.getValue("common2").asString().equals("3") &&
arg.getValue("common3").asString().equals("4") &&
arg.getValue("common4").asString().equals("3") &&
arg.getValue("common5").asString().equals("4") &&
arg.getValue("common6").asString().equals("4");
}));
}
@Specification(number="3.3.1.1", text="The API SHOULD have a method for setting a transaction context propagator.")