MultiProvider: Added MultiProviderMetadata.java, refactored tests and removed json dep
Signed-off-by: suvaidkhan <khansuvaid@yahoo.com>
This commit is contained in:
parent
58c308c25e
commit
11039490bb
6
pom.xml
6
pom.xml
|
|
@ -70,12 +70,6 @@
|
|||
<version>2.0.17</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20250517</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test -->
|
||||
<dependency>
|
||||
<groupId>com.tngtech.archunit</groupId>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ import dev.openfeature.sdk.Metadata;
|
|||
import dev.openfeature.sdk.ProviderEvaluation;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import dev.openfeature.sdk.exceptions.GeneralError;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -19,7 +21,6 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.Future;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/** <b>Experimental:</b> Provider implementation for Multi-provider. */
|
||||
@Slf4j
|
||||
|
|
@ -31,10 +32,10 @@ public class MultiProvider extends EventProvider {
|
|||
public static final int INIT_THREADS_COUNT = 8;
|
||||
private final Map<String, FeatureProvider> providers;
|
||||
private final Strategy strategy;
|
||||
private String metadataName;
|
||||
private MultiProviderMetadata metadata;
|
||||
|
||||
/**
|
||||
* Constructs a MultiProvider with the given list of FeatureProviders, using a default strategy.
|
||||
* Constructs a MultiProvider with the given list of FeatureProviders, by default uses FirstMatchStrategy.
|
||||
*
|
||||
* @param providers the list of FeatureProviders to initialize the MultiProvider with
|
||||
*/
|
||||
|
|
@ -77,33 +78,34 @@ public class MultiProvider extends EventProvider {
|
|||
*/
|
||||
@Override
|
||||
public void initialize(EvaluationContext evaluationContext) throws Exception {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("name", NAME);
|
||||
JSONObject providersMetadata = new JSONObject();
|
||||
json.put("originalMetadata", providersMetadata);
|
||||
ExecutorService initPool = Executors.newFixedThreadPool(INIT_THREADS_COUNT);
|
||||
var metadataBuilder = MultiProviderMetadata.builder();
|
||||
metadataBuilder.name(NAME);
|
||||
HashMap<String, Metadata> providersMetadata = new HashMap<>();
|
||||
ExecutorService initPool = Executors.newFixedThreadPool(Math.min(INIT_THREADS_COUNT, providers.size()));
|
||||
Collection<Callable<Boolean>> tasks = new ArrayList<>(providers.size());
|
||||
for (FeatureProvider provider : providers.values()) {
|
||||
tasks.add(() -> {
|
||||
provider.initialize(evaluationContext);
|
||||
return true;
|
||||
});
|
||||
JSONObject providerMetadata = new JSONObject();
|
||||
providerMetadata.put("name", provider.getMetadata().getName());
|
||||
providersMetadata.put(provider.getMetadata().getName(), providerMetadata);
|
||||
Metadata providerMetadata = provider.getMetadata();
|
||||
providersMetadata.put(providerMetadata.getName(), providerMetadata);
|
||||
}
|
||||
metadataBuilder.originalMetadata(providersMetadata);
|
||||
List<Future<Boolean>> results = initPool.invokeAll(tasks);
|
||||
for (Future<Boolean> result : results) {
|
||||
if (!result.get()) {
|
||||
throw new GeneralError("init failed");
|
||||
}
|
||||
}
|
||||
metadataName = json.toString();
|
||||
initPool.shutdown();
|
||||
metadata = metadataBuilder.build();
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(value = "EI_EXPOSE_REP")
|
||||
@Override
|
||||
public Metadata getMetadata() {
|
||||
return () -> metadataName;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package dev.openfeature.sdk.multiprovider;
|
||||
|
||||
import dev.openfeature.sdk.Metadata;
|
||||
import java.util.Map;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Metadata class for Multiprovider.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class MultiProviderMetadata implements Metadata {
|
||||
String name;
|
||||
Map<String, Metadata> originalMetadata;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package dev.openfeature.sdk.multiProvider;
|
||||
|
||||
import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import dev.openfeature.sdk.ErrorCode;
|
||||
import dev.openfeature.sdk.EvaluationContext;
|
||||
import dev.openfeature.sdk.FeatureProvider;
|
||||
import dev.openfeature.sdk.Metadata;
|
||||
import dev.openfeature.sdk.MutableContext;
|
||||
import dev.openfeature.sdk.ProviderEvaluation;
|
||||
import dev.openfeature.sdk.Value;
|
||||
import dev.openfeature.sdk.providers.memory.Flag;
|
||||
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
public abstract class BaseStrategyTest {
|
||||
|
||||
protected FeatureProvider mockProvider1;
|
||||
protected FeatureProvider mockProvider2;
|
||||
protected FeatureProvider mockProvider3;
|
||||
|
||||
protected Metadata mockMetaData1;
|
||||
protected Metadata mockMetaData2;
|
||||
protected Metadata mockMetaData3;
|
||||
|
||||
protected InMemoryProvider inMemoryProvider1;
|
||||
protected InMemoryProvider inMemoryProvider2;
|
||||
|
||||
protected Map<String, FeatureProvider> orderedProviders;
|
||||
|
||||
protected EvaluationContext contextWithNewProvider;
|
||||
|
||||
protected static final String FLAG_KEY = "test-flag";
|
||||
protected static final String DEFAULT_STRING = "default";
|
||||
protected static final boolean DEFAULT_BOOLEAN = false;
|
||||
protected static final int DEFAULT_INTEGER = 0;
|
||||
protected static final double DEFAULT_DOUBLE = 0.0;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
setupMockProviders();
|
||||
setupInMemoryProviders();
|
||||
setupOrderedProviders();
|
||||
setupEvaluationContexts();
|
||||
}
|
||||
|
||||
protected void setupMockProviders() {
|
||||
mockProvider1 = mock(FeatureProvider.class);
|
||||
mockProvider2 = mock(FeatureProvider.class);
|
||||
mockProvider3 = mock(FeatureProvider.class);
|
||||
mockMetaData1 = mock(Metadata.class);
|
||||
mockMetaData2 = mock(Metadata.class);
|
||||
mockMetaData3 = mock(Metadata.class);
|
||||
when(mockMetaData1.getName()).thenReturn("provider1");
|
||||
when(mockMetaData2.getName()).thenReturn("provider2");
|
||||
when(mockMetaData3.getName()).thenReturn("provider3");
|
||||
when(mockProvider1.getMetadata()).thenReturn(mockMetaData1);
|
||||
when(mockProvider2.getMetadata()).thenReturn(mockMetaData2);
|
||||
when(mockProvider3.getMetadata()).thenReturn(mockMetaData3);
|
||||
}
|
||||
|
||||
protected void setupInMemoryProviders() {
|
||||
Map<String, Flag<?>> flags1 = createFlags1();
|
||||
Map<String, Flag<?>> flags2 = createFlags2();
|
||||
|
||||
inMemoryProvider1 = new InMemoryProvider(flags1) {
|
||||
public Metadata getMetadata() {
|
||||
return () -> "old-provider";
|
||||
}
|
||||
};
|
||||
|
||||
inMemoryProvider2 = new InMemoryProvider(flags2) {
|
||||
public Metadata getMetadata() {
|
||||
return () -> "new-provider";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void setupOrderedProviders() {
|
||||
orderedProviders = new LinkedHashMap<>();
|
||||
orderedProviders.put("provider1", mockProvider1);
|
||||
orderedProviders.put("provider2", mockProvider2);
|
||||
orderedProviders.put("provider3", mockProvider3);
|
||||
}
|
||||
|
||||
protected void setupEvaluationContexts() {
|
||||
contextWithNewProvider = new MutableContext().add("provider", "new-provider");
|
||||
}
|
||||
|
||||
protected Map<String, Flag<?>> createFlags1() {
|
||||
Map<String, Flag<?>> flags = new HashMap<>();
|
||||
|
||||
flags.put(
|
||||
"b1",
|
||||
Flag.builder()
|
||||
.variant("on", true)
|
||||
.variant("off", false)
|
||||
.defaultVariant("on")
|
||||
.build());
|
||||
|
||||
flags.put(
|
||||
"i1",
|
||||
Flag.builder().variant("default", 1).defaultVariant("default").build());
|
||||
|
||||
flags.put(
|
||||
"d1",
|
||||
Flag.builder().variant("default", 1.0).defaultVariant("default").build());
|
||||
|
||||
flags.put(
|
||||
"s1",
|
||||
Flag.builder()
|
||||
.variant("default", "str1")
|
||||
.defaultVariant("default")
|
||||
.build());
|
||||
|
||||
flags.put(
|
||||
"o1",
|
||||
Flag.builder()
|
||||
.variant("default", new Value("v1"))
|
||||
.defaultVariant("default")
|
||||
.build());
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
protected Map<String, Flag<?>> createFlags2() {
|
||||
Map<String, Flag<?>> flags = new HashMap<>();
|
||||
|
||||
flags.put(
|
||||
"b1",
|
||||
Flag.builder()
|
||||
.variant("on", true)
|
||||
.variant("off", false)
|
||||
.defaultVariant("off")
|
||||
.build());
|
||||
|
||||
flags.put(
|
||||
"i1",
|
||||
Flag.builder().variant("default", 2).defaultVariant("default").build());
|
||||
|
||||
flags.put(
|
||||
"d1",
|
||||
Flag.builder().variant("default", 2.0).defaultVariant("default").build());
|
||||
|
||||
flags.put(
|
||||
"s1",
|
||||
Flag.builder()
|
||||
.variant("default", "str2")
|
||||
.defaultVariant("default")
|
||||
.build());
|
||||
|
||||
flags.put(
|
||||
"o1",
|
||||
Flag.builder()
|
||||
.variant("default", new Value("v2"))
|
||||
.defaultVariant("default")
|
||||
.build());
|
||||
|
||||
flags.put(
|
||||
"s2",
|
||||
Flag.builder()
|
||||
.variant("default", "s2str2")
|
||||
.defaultVariant("default")
|
||||
.build());
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
protected <T> ProviderEvaluation<T> createErrorResult(ErrorCode errorCode) {
|
||||
ProviderEvaluation<T> result = mock(ProviderEvaluation.class);
|
||||
when(result.getErrorCode()).thenReturn(errorCode);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void setupProviderFlagNotFound(FeatureProvider provider) {
|
||||
ProviderEvaluation<String> stringResult = createErrorResult(FLAG_NOT_FOUND);
|
||||
ProviderEvaluation<Boolean> booleanResult = createErrorResult(FLAG_NOT_FOUND);
|
||||
ProviderEvaluation<Integer> integerResult = createErrorResult(FLAG_NOT_FOUND);
|
||||
ProviderEvaluation<Double> doubleResult = createErrorResult(FLAG_NOT_FOUND);
|
||||
ProviderEvaluation<Value> objectResult = createErrorResult(FLAG_NOT_FOUND);
|
||||
|
||||
when(provider.getStringEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_STRING, null))
|
||||
.thenReturn(stringResult);
|
||||
when(provider.getBooleanEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_BOOLEAN, null))
|
||||
.thenReturn(booleanResult);
|
||||
when(provider.getIntegerEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_INTEGER, null))
|
||||
.thenReturn(integerResult);
|
||||
when(provider.getDoubleEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_DOUBLE, null))
|
||||
.thenReturn(doubleResult);
|
||||
when(provider.getObjectEvaluation(BaseStrategyTest.FLAG_KEY, null, null))
|
||||
.thenReturn(objectResult);
|
||||
}
|
||||
|
||||
protected void setupProviderError(FeatureProvider provider, ErrorCode errorCode) {
|
||||
ProviderEvaluation<String> result = createErrorResult(errorCode);
|
||||
when(provider.getStringEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_STRING, null))
|
||||
.thenReturn(result);
|
||||
}
|
||||
|
||||
protected void setupProviderSuccess(FeatureProvider provider, String value) {
|
||||
ProviderEvaluation<String> result = mock(ProviderEvaluation.class);
|
||||
when(result.getErrorCode()).thenReturn(null);
|
||||
when(result.getValue()).thenReturn(value);
|
||||
when(provider.getStringEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_STRING, null))
|
||||
.thenReturn(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package dev.openfeature.sdk.multiProvider;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import dev.openfeature.sdk.ErrorCode;
|
||||
import dev.openfeature.sdk.ProviderEvaluation;
|
||||
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
|
||||
import dev.openfeature.sdk.multiprovider.FirstMatchStrategy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FirstMatchStrategyTest extends BaseStrategyTest {
|
||||
|
||||
private final FirstMatchStrategy strategy = new FirstMatchStrategy();
|
||||
|
||||
@Test
|
||||
void shouldSkipFlagNotFoundAndReturnFirstMatch() {
|
||||
setupProviderFlagNotFound(mockProvider1);
|
||||
setupProviderSuccess(mockProvider2, "success");
|
||||
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("success", result.getValue());
|
||||
assertNull(result.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFirstNonFlagNotFoundError() {
|
||||
setupProviderError(mockProvider1, ErrorCode.PARSE_ERROR);
|
||||
setupProviderSuccess(mockProvider2, "success");
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(ErrorCode.PARSE_ERROR, result.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnSuccessWhenFirstProviderSucceeds() {
|
||||
setupProviderSuccess(mockProvider1, "first-success");
|
||||
setupProviderFlagNotFound(mockProvider2);
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("first-success", result.getValue());
|
||||
assertNull(result.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowFlagNotFoundWhenAllProvidersReturnFlagNotFound() {
|
||||
setupProviderFlagNotFound(mockProvider1);
|
||||
setupProviderFlagNotFound(mockProvider2);
|
||||
setupProviderFlagNotFound(mockProvider3);
|
||||
FlagNotFoundError exception = assertThrows(FlagNotFoundError.class, () -> {
|
||||
strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
});
|
||||
|
||||
assertEquals("flag not found", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipMultipleFlagNotFoundAndReturnFirstOtherError() {
|
||||
setupProviderFlagNotFound(mockProvider1);
|
||||
setupProviderFlagNotFound(mockProvider2);
|
||||
setupProviderError(mockProvider3, ErrorCode.PARSE_ERROR);
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
assertNotNull(result);
|
||||
assertEquals(ErrorCode.PARSE_ERROR, result.getErrorCode());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package dev.openfeature.sdk.multiProvider;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import dev.openfeature.sdk.ErrorCode;
|
||||
import dev.openfeature.sdk.ProviderEvaluation;
|
||||
import dev.openfeature.sdk.exceptions.GeneralError;
|
||||
import dev.openfeature.sdk.multiprovider.FirstSuccessfulStrategy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FirstSuccessfulStrategyTest extends BaseStrategyTest {
|
||||
|
||||
private final FirstSuccessfulStrategy strategy = new FirstSuccessfulStrategy();
|
||||
|
||||
@Test
|
||||
void shouldSkipFlagNotFoundAndReturnFirstSuccess() {
|
||||
setupProviderFlagNotFound(mockProvider1);
|
||||
setupProviderSuccess(mockProvider2, "success");
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
assertNotNull(result);
|
||||
assertEquals("success", result.getValue());
|
||||
assertNull(result.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowGeneralErrorWhenAllProvidersFail() {
|
||||
setupProviderFlagNotFound(mockProvider1);
|
||||
setupProviderError(mockProvider2, ErrorCode.PARSE_ERROR);
|
||||
setupProviderError(mockProvider3, ErrorCode.TYPE_MISMATCH);
|
||||
GeneralError exception = assertThrows(GeneralError.class, () -> {
|
||||
strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
});
|
||||
|
||||
assertEquals("evaluation error", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipProvidersThatOnlyReturnErrors() {
|
||||
setupProviderError(mockProvider1, ErrorCode.INVALID_CONTEXT);
|
||||
setupProviderError(mockProvider2, ErrorCode.PROVIDER_NOT_READY);
|
||||
setupProviderError(mockProvider3, ErrorCode.GENERAL);
|
||||
|
||||
assertThrows(GeneralError.class, () -> {
|
||||
strategy.evaluate(
|
||||
orderedProviders,
|
||||
FLAG_KEY,
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation(FLAG_KEY, DEFAULT_STRING, null));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowGeneralErrorForNonExistentFlag() {
|
||||
orderedProviders.clear();
|
||||
orderedProviders.put("old-provider", inMemoryProvider1);
|
||||
orderedProviders.put("new-provider", inMemoryProvider2);
|
||||
assertThrows(GeneralError.class, () -> {
|
||||
strategy.evaluate(
|
||||
orderedProviders,
|
||||
"non-existent-flag",
|
||||
DEFAULT_STRING,
|
||||
null,
|
||||
p -> p.getStringEvaluation("non-existent-flag", DEFAULT_STRING, null));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package dev.openfeature.sdk.multiProvider;
|
||||
|
||||
import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
|
@ -19,14 +18,10 @@ import dev.openfeature.sdk.Value;
|
|||
import dev.openfeature.sdk.exceptions.FlagNotFoundError;
|
||||
import dev.openfeature.sdk.exceptions.GeneralError;
|
||||
import dev.openfeature.sdk.multiprovider.FirstMatchStrategy;
|
||||
import dev.openfeature.sdk.multiprovider.FirstSuccessfulStrategy;
|
||||
import dev.openfeature.sdk.multiprovider.MultiProvider;
|
||||
import dev.openfeature.sdk.multiprovider.MultiProviderMetadata;
|
||||
import dev.openfeature.sdk.multiprovider.Strategy;
|
||||
import dev.openfeature.sdk.providers.memory.Flag;
|
||||
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
|
@ -34,129 +29,72 @@ import java.util.function.Function;
|
|||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MultiProviderTest {
|
||||
class MultiProviderTest extends BaseStrategyTest {
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
public void testInit() {
|
||||
FeatureProvider provider1 = mock(FeatureProvider.class);
|
||||
FeatureProvider provider2 = mock(FeatureProvider.class);
|
||||
when(provider1.getMetadata()).thenReturn(() -> "provider1");
|
||||
when(provider2.getMetadata()).thenReturn(() -> "provider2");
|
||||
|
||||
void shouldInitializeSuccessfully() {
|
||||
List<FeatureProvider> providers = new ArrayList<>(2);
|
||||
providers.add(provider1);
|
||||
providers.add(provider2);
|
||||
providers.add(mockProvider1);
|
||||
providers.add(mockProvider2);
|
||||
Strategy strategy = mock(Strategy.class);
|
||||
MultiProvider multiProvider = new MultiProvider(providers, strategy);
|
||||
multiProvider.initialize(null);
|
||||
|
||||
assertNotNull(multiProvider);
|
||||
assertEquals(
|
||||
"{\"originalMetadata\":{\"provider1\":{\"name\":\"provider1\"},"
|
||||
+ "\"provider2\":{\"name\":\"provider2\"}},\"name\":\"multiprovider\"}",
|
||||
multiProvider.getMetadata().getName());
|
||||
MultiProviderMetadata metadata = (MultiProviderMetadata) multiProvider.getMetadata();
|
||||
Map<String, Metadata> map = metadata.getOriginalMetadata();
|
||||
assertEquals(mockMetaData1, map.get(mockProvider1.getMetadata().getName()));
|
||||
assertEquals(mockMetaData2, map.get(mockProvider2.getMetadata().getName()));
|
||||
assertEquals("multiprovider", multiProvider.getMetadata().getName());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
public void testInitOneFails() {
|
||||
FeatureProvider provider1 = mock(FeatureProvider.class);
|
||||
FeatureProvider provider2 = mock(FeatureProvider.class);
|
||||
when(provider1.getMetadata()).thenReturn(() -> "provider1");
|
||||
when(provider2.getMetadata()).thenReturn(() -> "provider2");
|
||||
doThrow(new GeneralError()).when(provider1).initialize(any());
|
||||
doThrow(new GeneralError()).when(provider1).shutdown();
|
||||
|
||||
void shouldHandleInitializationFailure() {
|
||||
doThrow(new GeneralError()).when(mockProvider1).initialize(any());
|
||||
doThrow(new GeneralError()).when(mockProvider1).shutdown();
|
||||
List<FeatureProvider> providers = new ArrayList<>(2);
|
||||
providers.add(provider1);
|
||||
providers.add(provider2);
|
||||
providers.add(mockProvider1);
|
||||
providers.add(mockProvider2);
|
||||
Strategy strategy = mock(Strategy.class);
|
||||
MultiProvider multiProvider = new MultiProvider(providers, strategy);
|
||||
assertThrows(ExecutionException.class, () -> multiProvider.initialize(null));
|
||||
assertDoesNotThrow(() -> multiProvider.shutdown());
|
||||
assertDoesNotThrow(multiProvider::shutdown);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateProviderNames() {
|
||||
FeatureProvider provider1 = mock(FeatureProvider.class);
|
||||
FeatureProvider provider2 = mock(FeatureProvider.class);
|
||||
when(provider1.getMetadata()).thenReturn(() -> "provider");
|
||||
when(provider2.getMetadata()).thenReturn(() -> "provider");
|
||||
|
||||
void shouldHandleDuplicateProviderNames() {
|
||||
when(mockProvider1.getMetadata()).thenReturn(() -> "provider");
|
||||
when(mockProvider2.getMetadata()).thenReturn(() -> "provider");
|
||||
List<FeatureProvider> providers = new ArrayList<>(2);
|
||||
providers.add(provider1);
|
||||
providers.add(provider2);
|
||||
|
||||
providers.add(mockProvider1);
|
||||
providers.add(mockProvider2);
|
||||
assertDoesNotThrow(() -> new MultiProvider(providers, null).initialize(null));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
public void testRetrieveMetadataName() {
|
||||
void shouldRetrieveCorrectMetadataName() {
|
||||
List<FeatureProvider> providers = new ArrayList<>();
|
||||
FeatureProvider mockProvider = mock(FeatureProvider.class);
|
||||
when(mockProvider.getMetadata()).thenReturn(() -> "MockProvider");
|
||||
providers.add(mockProvider);
|
||||
providers.add(mockProvider1);
|
||||
Strategy mockStrategy = mock(Strategy.class);
|
||||
MultiProvider multiProvider = new MultiProvider(providers, mockStrategy);
|
||||
multiProvider.initialize(null);
|
||||
|
||||
assertEquals(
|
||||
"{\"originalMetadata\":{\"MockProvider\":{\"name\":\"MockProvider\"}}," + "\"name\":\"multiprovider\"}",
|
||||
multiProvider.getMetadata().getName());
|
||||
assertNotNull(multiProvider);
|
||||
MultiProviderMetadata metadata = (MultiProviderMetadata) multiProvider.getMetadata();
|
||||
Map<String, Metadata> map = metadata.getOriginalMetadata();
|
||||
assertEquals(mockMetaData1, map.get(mockProvider1.getMetadata().getName()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
public void testEvaluations() {
|
||||
Map<String, Flag<?>> flags1 = new HashMap<>();
|
||||
flags1.put(
|
||||
"b1",
|
||||
Flag.builder()
|
||||
.variant("true", true)
|
||||
.variant("false", false)
|
||||
.defaultVariant("true")
|
||||
.build());
|
||||
flags1.put("i1", Flag.builder().variant("v", 1).defaultVariant("v").build());
|
||||
flags1.put("d1", Flag.builder().variant("v", 1.0).defaultVariant("v").build());
|
||||
flags1.put("s1", Flag.builder().variant("v", "str1").defaultVariant("v").build());
|
||||
flags1.put(
|
||||
"o1",
|
||||
Flag.builder().variant("v", new Value("v1")).defaultVariant("v").build());
|
||||
InMemoryProvider provider1 = new InMemoryProvider(flags1) {
|
||||
public Metadata getMetadata() {
|
||||
return () -> "old-provider";
|
||||
}
|
||||
};
|
||||
Map<String, Flag<?>> flags2 = new HashMap<>();
|
||||
flags2.put(
|
||||
"b1",
|
||||
Flag.builder()
|
||||
.variant("true", true)
|
||||
.variant("false", false)
|
||||
.defaultVariant("false")
|
||||
.build());
|
||||
flags2.put("i1", Flag.builder().variant("v", 2).defaultVariant("v").build());
|
||||
flags2.put("d1", Flag.builder().variant("v", 2.0).defaultVariant("v").build());
|
||||
flags2.put("s1", Flag.builder().variant("v", "str2").defaultVariant("v").build());
|
||||
flags2.put(
|
||||
"o1",
|
||||
Flag.builder().variant("v", new Value("v2")).defaultVariant("v").build());
|
||||
|
||||
flags2.put(
|
||||
"s2", Flag.builder().variant("v", "s2str2").defaultVariant("v").build());
|
||||
InMemoryProvider provider2 = new InMemoryProvider(flags2) {
|
||||
public Metadata getMetadata() {
|
||||
return () -> "new-provider";
|
||||
}
|
||||
};
|
||||
void shouldUseDefaultFirstMatchStrategy() {
|
||||
List<FeatureProvider> providers = new ArrayList<>(2);
|
||||
providers.add(provider1);
|
||||
providers.add(provider2);
|
||||
providers.add(inMemoryProvider1);
|
||||
providers.add(inMemoryProvider2);
|
||||
MultiProvider multiProvider = new MultiProvider(providers);
|
||||
multiProvider.initialize(null);
|
||||
|
||||
assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null).getValue());
|
||||
assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null).getValue());
|
||||
assertEquals(1.0, multiProvider.getDoubleEvaluation("d1", 0.0, null).getValue());
|
||||
|
|
@ -164,28 +102,12 @@ class MultiProviderTest {
|
|||
assertEquals(
|
||||
"v1",
|
||||
multiProvider.getObjectEvaluation("o1", null, null).getValue().asString());
|
||||
assertThrows(FlagNotFoundError.class, () -> multiProvider.getStringEvaluation("non-existing", "", null));
|
||||
}
|
||||
|
||||
assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null).getValue());
|
||||
MultiProvider finalMultiProvider1 = multiProvider;
|
||||
assertThrows(FlagNotFoundError.class, () -> finalMultiProvider1.getStringEvaluation("non-existing", "", null));
|
||||
|
||||
multiProvider.shutdown();
|
||||
multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy());
|
||||
multiProvider.initialize(null);
|
||||
|
||||
assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null).getValue());
|
||||
assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null).getValue());
|
||||
assertEquals(1.0, multiProvider.getDoubleEvaluation("d1", 0.0, null).getValue());
|
||||
assertEquals("str1", multiProvider.getStringEvaluation("s1", "", null).getValue());
|
||||
assertEquals(
|
||||
"v1",
|
||||
multiProvider.getObjectEvaluation("o1", null, null).getValue().asString());
|
||||
|
||||
assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null).getValue());
|
||||
MultiProvider finalMultiProvider2 = multiProvider;
|
||||
assertThrows(GeneralError.class, () -> finalMultiProvider2.getStringEvaluation("non-existing", "", null));
|
||||
|
||||
multiProvider.shutdown();
|
||||
@SneakyThrows
|
||||
@Test
|
||||
void shouldWorkWithCustomStrategy() {
|
||||
Strategy customStrategy = new Strategy() {
|
||||
final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy();
|
||||
|
||||
|
|
@ -196,80 +118,41 @@ class MultiProviderTest {
|
|||
T defaultValue,
|
||||
EvaluationContext ctx,
|
||||
Function<FeatureProvider, ProviderEvaluation<T>> providerFunction) {
|
||||
|
||||
Value contextProvider = null;
|
||||
if (ctx != null) {
|
||||
contextProvider = ctx.getValue("provider");
|
||||
}
|
||||
|
||||
if (contextProvider != null && "new-provider".equals(contextProvider.asString())) {
|
||||
return providerFunction.apply(providers.get("new-provider"));
|
||||
}
|
||||
return fallbackStrategy.evaluate(providers, key, defaultValue, ctx, providerFunction);
|
||||
}
|
||||
};
|
||||
multiProvider = new MultiProvider(providers, customStrategy);
|
||||
multiProvider.initialize(null);
|
||||
|
||||
List<FeatureProvider> providers = new ArrayList<>(2);
|
||||
providers.add(inMemoryProvider1);
|
||||
providers.add(inMemoryProvider2);
|
||||
MultiProvider multiProvider = new MultiProvider(providers, customStrategy);
|
||||
multiProvider.initialize(null);
|
||||
EvaluationContext context = new MutableContext().add("provider", "new-provider");
|
||||
assertEquals(
|
||||
false, multiProvider.getBooleanEvaluation("b1", true, context).getValue());
|
||||
assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, null).getValue());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Test
|
||||
public void testFirstMatchStrategyErrorCode() {
|
||||
FeatureProvider provider1 = mock(FeatureProvider.class);
|
||||
FeatureProvider provider2 = mock(FeatureProvider.class);
|
||||
FeatureProvider provider3 = mock(FeatureProvider.class);
|
||||
|
||||
when(provider1.getMetadata()).thenReturn(() -> "provider1");
|
||||
when(provider2.getMetadata()).thenReturn(() -> "provider2");
|
||||
when(provider3.getMetadata()).thenReturn(() -> "provider3");
|
||||
|
||||
ProviderEvaluation<String> flagNotFoundResult = mock(ProviderEvaluation.class);
|
||||
when(flagNotFoundResult.getErrorCode()).thenReturn(FLAG_NOT_FOUND);
|
||||
|
||||
ProviderEvaluation<String> successResult = mock(ProviderEvaluation.class);
|
||||
when(successResult.getErrorCode()).thenReturn(null);
|
||||
when(successResult.getValue()).thenReturn("success");
|
||||
|
||||
when(provider1.getStringEvaluation("test", "default", null)).thenReturn(flagNotFoundResult);
|
||||
when(provider2.getStringEvaluation("test", "default", null)).thenReturn(successResult);
|
||||
|
||||
Map<String, FeatureProvider> providers = new LinkedHashMap<>();
|
||||
providers.put("provider1", provider1);
|
||||
providers.put("provider2", provider2);
|
||||
providers.put("provider3", provider3);
|
||||
FirstMatchStrategy strategy = new FirstMatchStrategy();
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
providers, "test", "default", null, p -> p.getStringEvaluation("test", "default", null));
|
||||
|
||||
assertEquals("success", result.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstSuccessfulStrategyErrorCode() {
|
||||
FeatureProvider provider1 = mock(FeatureProvider.class);
|
||||
FeatureProvider provider2 = mock(FeatureProvider.class);
|
||||
when(provider1.getMetadata()).thenReturn(() -> "provider1");
|
||||
when(provider2.getMetadata()).thenReturn(() -> "provider2");
|
||||
|
||||
ProviderEvaluation<String> flagNotFoundResult = mock(ProviderEvaluation.class);
|
||||
when(flagNotFoundResult.getErrorCode()).thenReturn(FLAG_NOT_FOUND);
|
||||
|
||||
ProviderEvaluation<String> successResult = mock(ProviderEvaluation.class);
|
||||
when(successResult.getErrorCode()).thenReturn(null);
|
||||
when(successResult.getValue()).thenReturn("success");
|
||||
|
||||
when(provider1.getStringEvaluation("test", "default", null)).thenReturn(flagNotFoundResult);
|
||||
when(provider2.getStringEvaluation("test", "default", null)).thenReturn(successResult);
|
||||
|
||||
Map<String, FeatureProvider> providers = new LinkedHashMap<>();
|
||||
providers.put("provider1", provider1);
|
||||
providers.put("provider2", provider2);
|
||||
FirstSuccessfulStrategy strategy = new FirstSuccessfulStrategy();
|
||||
ProviderEvaluation<String> result = strategy.evaluate(
|
||||
providers, "test", "default", null, p -> p.getStringEvaluation("test", "default", null));
|
||||
|
||||
assertEquals("success", result.getValue());
|
||||
void shouldSupportAllEvaluationTypes() {
|
||||
List<FeatureProvider> providers = new ArrayList<>(1);
|
||||
providers.add(inMemoryProvider1);
|
||||
MultiProvider multiProvider = new MultiProvider(providers);
|
||||
multiProvider.initialize(null);
|
||||
assertNotNull(multiProvider.getBooleanEvaluation("b1", false, null));
|
||||
assertNotNull(multiProvider.getIntegerEvaluation("i1", 0, null));
|
||||
assertNotNull(multiProvider.getDoubleEvaluation("d1", 0.0, null));
|
||||
assertNotNull(multiProvider.getStringEvaluation("s1", "", null));
|
||||
assertNotNull(multiProvider.getObjectEvaluation("o1", null, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue