MultiProvider: Added MultiProviderMetadata.java, refactored tests and removed json dep

Signed-off-by: suvaidkhan <khansuvaid@yahoo.com>
This commit is contained in:
suvaidkhan 2025-07-16 18:21:04 -05:00
parent 58c308c25e
commit 11039490bb
7 changed files with 478 additions and 189 deletions

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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));
});
}
}

View File

@ -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));
}
}