diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml
index 00dedd4e6..95b5929e0 100644
--- a/.github/workflows/pull_requests.yml
+++ b/.github/workflows/pull_requests.yml
@@ -3,10 +3,16 @@ on:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
- version: [ 8, 11, 17 ]
+ version: [8, 11, 17]
+ os: ["ubuntu", "windows", "macos"]
+ exclude:
+ - version: 8
+ os: macos
+ - version: 8
+ os: windows
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/pom.xml b/pom.xml
index e3755ef01..7e926ec38 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
5.10.3
4.12.0
UTF-8
- 5.1.5
+ 5.1.7
2.17.2
1.3.14
@@ -56,6 +56,12 @@
+
+ io.getunleash
+ yggdrasil-engine
+ 0.1.0-alpha.12
+ ${os.detected.classifier}
+
com.google.code.gson
gson
@@ -156,6 +162,13 @@
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.7.0
+
+
src/main/resources
diff --git a/src/main/java/io/getunleash/DefaultUnleash.java b/src/main/java/io/getunleash/DefaultUnleash.java
index 9abcdb9ba..abf82cc3d 100644
--- a/src/main/java/io/getunleash/DefaultUnleash.java
+++ b/src/main/java/io/getunleash/DefaultUnleash.java
@@ -3,22 +3,24 @@
import static io.getunleash.Variant.DISABLED_VARIANT;
import static java.util.Optional.ofNullable;
-import io.getunleash.event.*;
+import io.getunleash.engine.*;
+import io.getunleash.event.EventDispatcher;
+import io.getunleash.event.IsEnabledImpressionEvent;
+import io.getunleash.event.ToggleEvaluated;
+import io.getunleash.event.VariantImpressionEvent;
import io.getunleash.lang.Nullable;
import io.getunleash.metric.UnleashMetricService;
import io.getunleash.metric.UnleashMetricServiceImpl;
import io.getunleash.repository.FeatureRepository;
import io.getunleash.repository.IFeatureRepository;
+import io.getunleash.repository.JsonFeatureParser;
import io.getunleash.strategy.*;
-import io.getunleash.util.ConstraintMerger;
import io.getunleash.util.UnleashConfig;
-import io.getunleash.variant.VariantUtil;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,9 +41,9 @@ public class DefaultUnleash implements Unleash {
public static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy();
+ private final UnleashEngine unleashEngine;
private final UnleashMetricService metricService;
private final IFeatureRepository featureRepository;
- private final Map strategyMap;
private final UnleashContextProvider contextProvider;
private final EventDispatcher eventDispatcher;
private final UnleashConfig config;
@@ -64,7 +66,6 @@ public DefaultUnleash(
buildStrategyMap(strategies),
unleashConfig.getContextProvider(),
new EventDispatcher(unleashConfig),
- new UnleashMetricServiceImpl(unleashConfig, unleashConfig.getScheduledExecutor()),
false);
}
@@ -74,15 +75,13 @@ public DefaultUnleash(
IFeatureRepository featureRepository,
Map strategyMap,
UnleashContextProvider contextProvider,
- EventDispatcher eventDispatcher,
- UnleashMetricService metricService) {
+ EventDispatcher eventDispatcher) {
this(
unleashConfig,
featureRepository,
strategyMap,
contextProvider,
eventDispatcher,
- metricService,
false);
}
@@ -92,14 +91,33 @@ public DefaultUnleash(
Map strategyMap,
UnleashContextProvider contextProvider,
EventDispatcher eventDispatcher,
- UnleashMetricService metricService,
boolean failOnMultipleInstantiations) {
+
+ this.unleashEngine =
+ new UnleashEngine(
+ strategyMap.values().stream()
+ .map(YggdrasilAdapters::adapt)
+ .collect(Collectors.toList()),
+ Optional.ofNullable(unleashConfig.getFallbackStrategy())
+ .map(YggdrasilAdapters::adapt)
+ .orElse(null));
+ featureRepository.addConsumer(
+ featureCollection -> {
+ try {
+ this.unleashEngine.takeState(
+ JsonFeatureParser.toJsonString(featureCollection));
+ } catch (YggdrasilInvalidInputException e) {
+ LOGGER.error("Unable to update features", e);
+ }
+ });
+
this.config = unleashConfig;
this.featureRepository = featureRepository;
- this.strategyMap = strategyMap;
this.contextProvider = contextProvider;
this.eventDispatcher = eventDispatcher;
- this.metricService = metricService;
+ this.metricService =
+ new UnleashMetricServiceImpl(
+ unleashConfig, unleashConfig.getScheduledExecutor(), this.unleashEngine);
metricService.register(strategyMap.keySet());
initCounts.compute(
config.getClientIdentifier(),
@@ -139,204 +157,28 @@ public boolean isEnabled(
String toggleName,
UnleashContext context,
BiPredicate fallbackAction) {
- return isEnabled(toggleName, context, fallbackAction, false);
- }
-
- public boolean isEnabled(
- String toggleName,
- UnleashContext context,
- BiPredicate fallbackAction,
- boolean isParent) {
- FeatureEvaluationResult result =
- getFeatureEvaluationResult(toggleName, context, fallbackAction, null);
- if (!isParent) {
- count(toggleName, result.isEnabled());
- }
- eventDispatcher.dispatch(new ToggleEvaluated(toggleName, result.isEnabled()));
- dispatchEnabledImpressionDataIfNeeded("isEnabled", toggleName, result.isEnabled(), context);
- return result.isEnabled();
- }
-
- private void dispatchEnabledImpressionDataIfNeeded(
- String eventType, String toggleName, boolean enabled, UnleashContext context) {
- FeatureToggle toggle = featureRepository.getToggle(toggleName);
- if (toggle != null && toggle.hasImpressionData()) {
- eventDispatcher.dispatch(new IsEnabledImpressionEvent(toggleName, enabled, context));
- }
- }
-
- private FeatureEvaluationResult getFeatureEvaluationResult(
- String toggleName,
- UnleashContext context,
- BiPredicate fallbackAction,
- @Nullable Variant defaultVariant) {
- checkIfToggleMatchesNamePrefix(toggleName);
- FeatureToggle featureToggle = featureRepository.getToggle(toggleName);
-
- UnleashContext enhancedContext = context.applyStaticFields(config);
- if (featureToggle == null) {
- return new FeatureEvaluationResult(
- fallbackAction.test(toggleName, enhancedContext), defaultVariant);
- } else if (!featureToggle.isEnabled()) {
- return new FeatureEvaluationResult(false, defaultVariant);
- } else if (isParentDependencySatisfied(featureToggle, context, fallbackAction)) {
- // Dependent toggles, no point in evaluating child strategies if our dependencies are
- // not satisfied
- if (featureToggle.getStrategies().isEmpty()) {
- return new FeatureEvaluationResult(
- true, VariantUtil.selectVariant(featureToggle, context, defaultVariant));
- }
- for (ActivationStrategy strategy : featureToggle.getStrategies()) {
- Strategy configuredStrategy = getStrategy(strategy.getName());
- if (configuredStrategy == UNKNOWN_STRATEGY) {
- LOGGER.warn(
- "Unable to find matching strategy for toggle:{} strategy:{}",
- toggleName,
- strategy.getName());
- }
-
- FeatureEvaluationResult result =
- configuredStrategy.getResult(
- strategy.getParameters(),
- enhancedContext,
- ConstraintMerger.mergeConstraints(featureRepository, strategy),
- strategy.getVariants());
-
- if (result.isEnabled()) {
- Variant variant = result.getVariant();
- // If strategy variant is null, look for a variant in the featureToggle
- if (variant == null) {
- variant = VariantUtil.selectVariant(featureToggle, context, defaultVariant);
- }
- result.setVariant(variant);
- return result;
- }
- }
- }
- return new FeatureEvaluationResult(false, defaultVariant);
- }
-
- /**
- * Uses the old, statistically broken Variant seed for finding the correct variant
- *
- * @param toggleName Name of the toggle
- * @param context The UnleashContext
- * @param fallbackAction What to do if we fail to find the toggle
- * @param defaultVariant If we can't resolve a variant, what are we returning
- * @return A wrapper containing whether the feature was enabled as well which Variant was
- * selected
- * @deprecated
- */
- private FeatureEvaluationResult deprecatedGetFeatureEvaluationResult(
- String toggleName,
- UnleashContext context,
- BiPredicate fallbackAction,
- @Nullable Variant defaultVariant) {
- checkIfToggleMatchesNamePrefix(toggleName);
- FeatureToggle featureToggle = featureRepository.getToggle(toggleName);
UnleashContext enhancedContext = context.applyStaticFields(config);
- if (featureToggle == null) {
- return new FeatureEvaluationResult(
- fallbackAction.test(toggleName, enhancedContext), defaultVariant);
- } else if (!featureToggle.isEnabled()) {
- return new FeatureEvaluationResult(false, defaultVariant);
- } else if (isParentDependencySatisfied(featureToggle, context, fallbackAction)) {
- if (featureToggle.getStrategies().isEmpty()) {
- return new FeatureEvaluationResult(
- true,
- VariantUtil.selectDeprecatedVariantHashingAlgo(
- featureToggle, context, defaultVariant));
+ try {
+ Boolean enabled =
+ this.unleashEngine.isEnabled(
+ toggleName, YggdrasilAdapters.adapt(enhancedContext));
+ if (enabled == null) {
+ enabled = fallbackAction.test(toggleName, enhancedContext);
}
- for (ActivationStrategy strategy : featureToggle.getStrategies()) {
- Strategy configuredStrategy = getStrategy(strategy.getName());
- if (configuredStrategy == UNKNOWN_STRATEGY) {
- LOGGER.warn(
- "Unable to find matching strategy for toggle:{} strategy:{}",
- toggleName,
- strategy.getName());
- }
-
- FeatureEvaluationResult result =
- configuredStrategy.getDeprecatedHashingAlgoResult(
- strategy.getParameters(),
- enhancedContext,
- ConstraintMerger.mergeConstraints(featureRepository, strategy),
- strategy.getVariants());
-
- if (result.isEnabled()) {
- Variant variant = result.getVariant();
- // If strategy variant is null, look for a variant in the featureToggle
- if (variant == null) {
- variant =
- VariantUtil.selectDeprecatedVariantHashingAlgo(
- featureToggle, context, defaultVariant);
- }
- result.setVariant(variant);
- return result;
- }
- }
- }
- return new FeatureEvaluationResult(false, defaultVariant);
- }
-
- private boolean isParentDependencySatisfied(
- @Nonnull FeatureToggle featureToggle,
- @Nonnull UnleashContext context,
- BiPredicate fallbackAction) {
- if (!featureToggle.hasDependencies()) {
- return true;
- } else {
- return featureToggle.getDependencies().stream()
- .allMatch(
- parent -> {
- FeatureToggle parentToggle =
- featureRepository.getToggle(parent.getFeature());
- if (parentToggle == null) {
- LOGGER.warn(
- "Missing dependency [{}] for toggle: [{}]",
- parent.getFeature(),
- featureToggle.getName());
- return false;
- }
- if (!parentToggle.getDependencies().isEmpty()) {
- LOGGER.warn(
- "[{}] depends on feature [{}] which also depends on something. We don't currently support more than one level of dependency resolution",
- featureToggle.getName(),
- parent.getFeature());
- return false;
- }
- boolean parentSatisfied =
- isEnabled(
- parent.getFeature(), context, fallbackAction, true);
- if (parentSatisfied) {
- if (!parent.getVariants().isEmpty()) {
- return parent.getVariants()
- .contains(
- getVariant(
- parent.feature,
- context,
- DISABLED_VARIANT,
- true)
- .getName());
- } else {
- return parent.isEnabled();
- }
- } else {
- return !parent.isEnabled();
- }
- });
- }
- }
- private void checkIfToggleMatchesNamePrefix(String toggleName) {
- if (config.getNamePrefix() != null) {
- if (!toggleName.startsWith(config.getNamePrefix())) {
- LOGGER.warn(
- "Toggle [{}] doesnt start with configured name prefix of [{}] so it will always be disabled",
- toggleName,
- config.getNamePrefix());
+ this.unleashEngine.countToggle(toggleName, enabled);
+ eventDispatcher.dispatch(new ToggleEvaluated(toggleName, enabled));
+ if (this.unleashEngine.shouldEmitImpressionEvent(toggleName)) {
+ eventDispatcher.dispatch(
+ new IsEnabledImpressionEvent(toggleName, enabled, context));
}
+ return enabled;
+ } catch (YggdrasilInvalidInputException | YggdrasilError e) {
+ LOGGER.warn(
+ "A serious issue occurred when evaluating a feature toggle, defaulting to false",
+ e);
+ return false;
}
}
@@ -347,30 +189,33 @@ public Variant getVariant(String toggleName, UnleashContext context) {
@Override
public Variant getVariant(String toggleName, UnleashContext context, Variant defaultValue) {
- return getVariant(toggleName, context, defaultValue, false);
- }
-
- private Variant getVariant(
- String toggleName, UnleashContext context, Variant defaultValue, boolean isParent) {
- FeatureEvaluationResult result =
- getFeatureEvaluationResult(toggleName, context, (n, c) -> false, defaultValue);
- Variant variant = result.getVariant();
- if (!isParent) {
- metricService.countVariant(toggleName, variant.getName());
- // Should count yes/no also when getting variant.
- metricService.count(toggleName, result.isEnabled());
- }
- dispatchVariantImpressionDataIfNeeded(
- toggleName, variant.getName(), result.isEnabled(), context);
- return variant;
- }
+ UnleashContext enhancedContext = context.applyStaticFields(config);
- private void dispatchVariantImpressionDataIfNeeded(
- String toggleName, String variantName, boolean enabled, UnleashContext context) {
- FeatureToggle toggle = featureRepository.getToggle(toggleName);
- if (toggle != null && toggle.hasImpressionData()) {
- eventDispatcher.dispatch(
- new VariantImpressionEvent(toggleName, enabled, context, variantName));
+ try {
+ Context adaptedContext = YggdrasilAdapters.adapt(enhancedContext);
+
+ Variant variant =
+ YggdrasilAdapters.adapt(
+ this.unleashEngine.getVariant(toggleName, adaptedContext),
+ defaultValue);
+
+ this.unleashEngine.countToggle(toggleName, variant.isFeatureEnabled());
+ this.unleashEngine.countVariant(toggleName, variant.getName());
+ eventDispatcher.dispatch(new ToggleEvaluated(toggleName, variant.isEnabled()));
+ if (unleashEngine.shouldEmitImpressionEvent(toggleName)) {
+ eventDispatcher.dispatch(
+ new VariantImpressionEvent(
+ toggleName,
+ variant.isFeatureEnabled(),
+ context,
+ variant.getName()));
+ }
+ return variant;
+ } catch (YggdrasilInvalidInputException | YggdrasilError e) {
+ LOGGER.warn(
+ "A serious issue occurred when evaluating a variant, defaulting to the default value",
+ e);
+ return defaultValue;
}
}
@@ -384,75 +229,6 @@ public Variant getVariant(String toggleName, Variant defaultValue) {
return getVariant(toggleName, contextProvider.getContext(), defaultValue);
}
- /**
- * Uses the old, statistically broken Variant seed for finding the correct variant
- *
- * @param toggleName
- * @param context
- * @return
- * @deprecated
- */
- @Override
- public Variant deprecatedGetVariant(String toggleName, UnleashContext context) {
- return deprecatedGetVariant(toggleName, context, DISABLED_VARIANT);
- }
-
- /**
- * Uses the old, statistically broken Variant seed for finding the correct variant
- *
- * @param toggleName
- * @param context
- * @param defaultValue
- * @return
- * @deprecated
- */
- @Override
- public Variant deprecatedGetVariant(
- String toggleName, UnleashContext context, Variant defaultValue) {
- return deprecatedGetVariant(toggleName, context, defaultValue, false);
- }
-
- private Variant deprecatedGetVariant(
- String toggleName, UnleashContext context, Variant defaultValue, boolean isParent) {
- FeatureEvaluationResult result =
- deprecatedGetFeatureEvaluationResult(
- toggleName, context, (n, c) -> false, defaultValue);
- Variant variant = result.getVariant();
- if (!isParent) {
- metricService.countVariant(toggleName, variant.getName());
- // Should count yes/no also when getting variant.
- metricService.count(toggleName, result.isEnabled());
- }
- dispatchVariantImpressionDataIfNeeded(
- toggleName, variant.getName(), result.isEnabled(), context);
- return variant;
- }
-
- /**
- * Uses the old, statistically broken Variant seed for finding the correct variant
- *
- * @param toggleName
- * @return
- * @deprecated
- */
- @Override
- public Variant deprecatedGetVariant(String toggleName) {
- return deprecatedGetVariant(toggleName, contextProvider.getContext());
- }
-
- /**
- * Uses the old, statistically broken Variant seed for finding the correct variant
- *
- * @param toggleName
- * @param defaultValue
- * @return
- * @deprecated
- */
- @Override
- public Variant deprecatedGetVariant(String toggleName, Variant defaultValue) {
- return deprecatedGetVariant(toggleName, contextProvider.getContext(), defaultValue);
- }
-
/**
* Use more().getFeatureToggleDefinition() instead
*
@@ -473,12 +249,6 @@ public List getFeatureToggleNames() {
return featureRepository.getFeatureNames();
}
- /** Use more().count() instead */
- @Deprecated
- public void count(final String toggleName, boolean enabled) {
- metricService.count(toggleName, enabled);
- }
-
private static Map buildStrategyMap(@Nullable Strategy[] strategies) {
Map map = new HashMap<>();
@@ -493,10 +263,6 @@ private static Map buildStrategyMap(@Nullable Strategy[] strat
return map;
}
- private Strategy getStrategy(String strategy) {
- return strategyMap.getOrDefault(strategy, config.getFallbackStrategy());
- }
-
@Override
public void shutdown() {
config.getScheduledExecutor().shutdown();
@@ -529,24 +295,12 @@ public List evaluateAllToggles(UnleashContext context) {
return getFeatureToggleNames().stream()
.map(
toggleName -> {
- FeatureEvaluationResult result =
- getFeatureEvaluationResult(
- toggleName, context, (n, c) -> false, null);
-
- return new EvaluatedToggle(
- toggleName, result.isEnabled(), result.getVariant());
+ boolean enabled =
+ isEnabled(toggleName, context, (name, ctx) -> false);
+ Variant variant = getVariant(toggleName, context, DISABLED_VARIANT);
+ return new EvaluatedToggle(toggleName, enabled, variant);
})
.collect(Collectors.toList());
}
-
- @Override
- public void count(final String toggleName, boolean enabled) {
- metricService.count(toggleName, enabled);
- }
-
- @Override
- public void countVariant(final String toggleName, String variantName) {
- metricService.countVariant(toggleName, variantName);
- }
}
}
diff --git a/src/main/java/io/getunleash/FakeUnleash.java b/src/main/java/io/getunleash/FakeUnleash.java
index 0e88d135a..64eb03e7f 100644
--- a/src/main/java/io/getunleash/FakeUnleash.java
+++ b/src/main/java/io/getunleash/FakeUnleash.java
@@ -107,17 +107,6 @@ public void disableAllExcept(String... excludedFeatures) {
}
}
- @Override
- public Variant deprecatedGetVariant(String toggleName, UnleashContext context) {
- return null;
- }
-
- @Override
- public Variant deprecatedGetVariant(
- String toggleName, UnleashContext context, Variant defaultValue) {
- return null;
- }
-
public void resetAll() {
disableAll = false;
enableAll = false;
@@ -176,15 +165,5 @@ public List evaluateAllToggles(@Nullable UnleashContext context
getVariant(toggleName)))
.collect(Collectors.toList());
}
-
- @Override
- public void count(String toggleName, boolean enabled) {
- // Nothing to count
- }
-
- @Override
- public void countVariant(String toggleName, String variantName) {
- // Nothing to count
- }
}
}
diff --git a/src/main/java/io/getunleash/MoreOperations.java b/src/main/java/io/getunleash/MoreOperations.java
index ca37fdfd5..a362ac065 100644
--- a/src/main/java/io/getunleash/MoreOperations.java
+++ b/src/main/java/io/getunleash/MoreOperations.java
@@ -19,8 +19,4 @@ public interface MoreOperations {
* @return
*/
List evaluateAllToggles(UnleashContext context);
-
- void count(String toggleName, boolean enabled);
-
- void countVariant(String toggleName, String variantName);
}
diff --git a/src/main/java/io/getunleash/Unleash.java b/src/main/java/io/getunleash/Unleash.java
index 79fe6964e..6e5259799 100644
--- a/src/main/java/io/getunleash/Unleash.java
+++ b/src/main/java/io/getunleash/Unleash.java
@@ -43,19 +43,6 @@ default Variant getVariant(final String toggleName, final Variant defaultValue)
return getVariant(toggleName, UnleashContext.builder().build(), defaultValue);
}
- Variant deprecatedGetVariant(final String toggleName, final UnleashContext context);
-
- Variant deprecatedGetVariant(
- final String toggleName, final UnleashContext context, final Variant defaultValue);
-
- default Variant deprecatedGetVariant(final String toggleName) {
- return deprecatedGetVariant(toggleName, UnleashContext.builder().build());
- }
-
- default Variant deprecatedGetVariant(final String toggleName, final Variant defaultValue) {
- return deprecatedGetVariant(toggleName, UnleashContext.builder().build(), defaultValue);
- }
-
/**
* Use more().getFeatureToggleNames() instead
*
diff --git a/src/main/java/io/getunleash/Variant.java b/src/main/java/io/getunleash/Variant.java
index 2d8e2760b..d36e36bc3 100644
--- a/src/main/java/io/getunleash/Variant.java
+++ b/src/main/java/io/getunleash/Variant.java
@@ -12,27 +12,41 @@ public class Variant {
@Nullable private final Payload payload;
private final boolean enabled;
@Nullable private final String stickiness;
+ private final boolean feature_enabled;
- public Variant(String name, @Nullable Payload payload, boolean enabled) {
- this(name, payload, enabled, null);
+ public Variant(
+ String name, @Nullable Payload payload, boolean enabled, boolean feature_enabled) {
+ this(name, payload, enabled, null, feature_enabled);
}
- public Variant(String name, @Nullable Payload payload, boolean enabled, String stickiness) {
+ public Variant(
+ String name,
+ @Nullable Payload payload,
+ boolean enabled,
+ String stickiness,
+ boolean feature_enabled) {
this.name = name;
this.payload = payload;
this.enabled = enabled;
this.stickiness = stickiness;
+ this.feature_enabled = feature_enabled;
}
public Variant(String name, @Nullable String payload, boolean enabled) {
- this(name, payload, enabled, null);
+ this(name, payload, enabled, null, false);
}
- public Variant(String name, @Nullable String payload, boolean enabled, String stickiness) {
+ public Variant(
+ String name,
+ @Nullable String payload,
+ boolean enabled,
+ String stickiness,
+ boolean feature_enabled) {
this.name = name;
this.payload = new Payload("string", payload);
this.enabled = enabled;
this.stickiness = stickiness;
+ this.feature_enabled = feature_enabled;
}
public String getName() {
@@ -47,6 +61,10 @@ public boolean isEnabled() {
return enabled;
}
+ public boolean isFeatureEnabled() {
+ return feature_enabled;
+ }
+
@Nullable
public String getStickiness() {
return stickiness;
diff --git a/src/main/java/io/getunleash/YggdrasilAdapters.java b/src/main/java/io/getunleash/YggdrasilAdapters.java
new file mode 100644
index 000000000..af9f6d503
--- /dev/null
+++ b/src/main/java/io/getunleash/YggdrasilAdapters.java
@@ -0,0 +1,90 @@
+package io.getunleash;
+
+import io.getunleash.engine.Context;
+import io.getunleash.engine.IStrategy;
+import io.getunleash.engine.Payload;
+import io.getunleash.engine.VariantDef;
+import io.getunleash.lang.Nullable;
+import io.getunleash.strategy.Strategy;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Map;
+import java.util.Optional;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class YggdrasilAdapters {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUnleash.class);
+
+ @NotNull
+ public static IStrategy adapt(Strategy s) {
+ return new IStrategy() {
+ @Override
+ public String getName() {
+ return s.getName();
+ }
+
+ @Override
+ public boolean isEnabled(Map map, Context context) {
+ return s.isEnabled(map, adapt(context));
+ }
+ };
+ }
+
+ public static UnleashContext adapt(Context context) {
+ ZonedDateTime currentTime = ZonedDateTime.now();
+ if (context.getCurrentTime() != null) {
+ try {
+ currentTime = ZonedDateTime.parse(context.getCurrentTime());
+ } catch (DateTimeParseException e) {
+ LOGGER.warn(
+ "Could not parse current time from context, falling back to system time: ",
+ context.getCurrentTime());
+ currentTime = ZonedDateTime.now();
+ }
+ }
+
+ return new UnleashContext(
+ context.getAppName(),
+ context.getEnvironment(),
+ context.getUserId(),
+ context.getSessionId(),
+ context.getRemoteAddress(),
+ currentTime,
+ context.getProperties());
+ }
+
+ public static Context adapt(UnleashContext context) {
+ Context mapped = new Context();
+ mapped.setAppName(context.getAppName().orElse(null));
+ mapped.setEnvironment(context.getEnvironment().orElse(null));
+ mapped.setUserId(context.getUserId().orElse(null));
+ mapped.setSessionId(context.getSessionId().orElse(null));
+ mapped.setRemoteAddress(context.getRemoteAddress().orElse(null));
+ mapped.setProperties(context.getProperties());
+ mapped.setCurrentTime(
+ DateTimeFormatter.ISO_DATE_TIME.format(
+ context.getCurrentTime().orElse(ZonedDateTime.now())));
+ return mapped;
+ }
+
+ public static Variant adapt(VariantDef variant, Variant defaultValue) {
+ if (variant == null) {
+ return defaultValue;
+ }
+ return new Variant(
+ variant.getName(),
+ adapt(variant.getPayload()),
+ variant.isEnabled(),
+ variant.isFeatureEnabled());
+ }
+
+ public static @Nullable io.getunleash.variant.Payload adapt(@Nullable Payload payload) {
+ return Optional.ofNullable(payload)
+ .map(p -> new io.getunleash.variant.Payload(p.getType(), p.getValue()))
+ .orElse(new io.getunleash.variant.Payload("string", null));
+ }
+}
diff --git a/src/main/java/io/getunleash/metric/ClientMetrics.java b/src/main/java/io/getunleash/metric/ClientMetrics.java
index 82eac44e9..8d70070a8 100644
--- a/src/main/java/io/getunleash/metric/ClientMetrics.java
+++ b/src/main/java/io/getunleash/metric/ClientMetrics.java
@@ -1,5 +1,6 @@
package io.getunleash.metric;
+import io.getunleash.engine.MetricsBucket;
import io.getunleash.event.UnleashEvent;
import io.getunleash.event.UnleashSubscriber;
import io.getunleash.lang.Nullable;
diff --git a/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java b/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java
index a69b5c389..8c0abde25 100644
--- a/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java
+++ b/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java
@@ -5,12 +5,14 @@
import io.getunleash.event.EventDispatcher;
import io.getunleash.util.AtomicLongSerializer;
import io.getunleash.util.DateTimeSerializer;
+import io.getunleash.util.InstantSerializer;
import io.getunleash.util.UnleashConfig;
import io.getunleash.util.UnleashURLs;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.time.Instant;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicLong;
@@ -33,6 +35,7 @@ public DefaultHttpMetricsSender(UnleashConfig unleashConfig) {
this.gson =
new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new DateTimeSerializer())
+ .registerTypeAdapter(Instant.class, new InstantSerializer())
.registerTypeAdapter(AtomicLong.class, new AtomicLongSerializer())
.create();
}
diff --git a/src/main/java/io/getunleash/metric/MetricsBucket.java b/src/main/java/io/getunleash/metric/MetricsBucket.java
deleted file mode 100644
index be09773f6..000000000
--- a/src/main/java/io/getunleash/metric/MetricsBucket.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package io.getunleash.metric;
-
-import io.getunleash.lang.Nullable;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-class MetricsBucket {
- private final ConcurrentMap toggles;
- private final LocalDateTime start;
- @Nullable private volatile LocalDateTime stop;
-
- MetricsBucket() {
- this.start = LocalDateTime.now(ZoneId.of("UTC"));
- this.toggles = new ConcurrentHashMap<>();
- }
-
- void registerCount(String toggleName, boolean active) {
- getOrCreate(toggleName).register(active);
- }
-
- void registerCount(String toggleName, String variantName) {
- getOrCreate(toggleName).register(variantName);
- }
-
- private ToggleCount getOrCreate(String toggleName) {
- return toggles.computeIfAbsent(toggleName, s -> new ToggleCount());
- }
-
- void end() {
- this.stop = LocalDateTime.now(ZoneId.of("UTC"));
- }
-
- public Map getToggles() {
- return toggles;
- }
-
- public LocalDateTime getStart() {
- return start;
- }
-
- public @Nullable LocalDateTime getStop() {
- return stop;
- }
-}
diff --git a/src/main/java/io/getunleash/metric/UnleashMetricService.java b/src/main/java/io/getunleash/metric/UnleashMetricService.java
index 1865c3b82..d3602b7df 100644
--- a/src/main/java/io/getunleash/metric/UnleashMetricService.java
+++ b/src/main/java/io/getunleash/metric/UnleashMetricService.java
@@ -4,8 +4,4 @@
public interface UnleashMetricService {
void register(Set strategies);
-
- void count(String toggleName, boolean active);
-
- void countVariant(String toggleName, String variantName);
}
diff --git a/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java b/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java
index caf2c3b0e..bf735c3fb 100644
--- a/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java
+++ b/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java
@@ -1,5 +1,8 @@
package io.getunleash.metric;
+import io.getunleash.engine.MetricsBucket;
+import io.getunleash.engine.UnleashEngine;
+import io.getunleash.engine.YggdrasilError;
import io.getunleash.util.Throttler;
import io.getunleash.util.UnleashConfig;
import io.getunleash.util.UnleashScheduledExecutor;
@@ -15,21 +18,25 @@ public class UnleashMetricServiceImpl implements UnleashMetricService {
private final UnleashConfig unleashConfig;
private final MetricSender metricSender;
- // mutable
- private volatile MetricsBucket currentMetricsBucket;
+ // synchronization is handled in the engine itself
+ private final UnleashEngine engine;
private final Throttler throttler;
public UnleashMetricServiceImpl(
- UnleashConfig unleashConfig, UnleashScheduledExecutor executor) {
- this(unleashConfig, unleashConfig.getMetricSenderFactory().apply(unleashConfig), executor);
+ UnleashConfig unleashConfig, UnleashScheduledExecutor executor, UnleashEngine engine) {
+ this(
+ unleashConfig,
+ unleashConfig.getMetricSenderFactory().apply(unleashConfig),
+ executor,
+ engine);
}
public UnleashMetricServiceImpl(
UnleashConfig unleashConfig,
MetricSender metricSender,
- UnleashScheduledExecutor executor) {
- this.currentMetricsBucket = new MetricsBucket();
+ UnleashScheduledExecutor executor,
+ UnleashEngine engine) {
this.started = LocalDateTime.now(ZoneId.of("UTC"));
this.unleashConfig = unleashConfig;
this.metricSender = metricSender;
@@ -38,6 +45,7 @@ public UnleashMetricServiceImpl(
(int) unleashConfig.getSendMetricsInterval(),
300,
unleashConfig.getUnleashURLs().getClientMetricsURL());
+ this.engine = engine;
long metricsInterval = unleashConfig.getSendMetricsInterval();
executor.setInterval(sendMetrics(), metricsInterval, metricsInterval);
@@ -50,29 +58,24 @@ public void register(Set strategies) {
metricSender.registerClient(registration);
}
- @Override
- public void count(String toggleName, boolean active) {
- currentMetricsBucket.registerCount(toggleName, active);
- }
-
- @Override
- public void countVariant(String toggleName, String variantName) {
- currentMetricsBucket.registerCount(toggleName, variantName);
- }
-
private Runnable sendMetrics() {
return () -> {
if (throttler.performAction()) {
- MetricsBucket metricsBucket = this.currentMetricsBucket;
- this.currentMetricsBucket = new MetricsBucket();
- metricsBucket.end();
- ClientMetrics metrics = new ClientMetrics(unleashConfig, metricsBucket);
- int statusCode = metricSender.sendMetrics(metrics);
- if (statusCode >= 200 && statusCode < 400) {
- throttler.decrementFailureCountAndResetSkips();
- }
- if (statusCode >= 400) {
- throttler.handleHttpErrorCodes(statusCode);
+ try {
+ MetricsBucket bucket = this.engine.getMetrics();
+
+ ClientMetrics metrics = new ClientMetrics(unleashConfig, bucket);
+ int statusCode = metricSender.sendMetrics(metrics);
+ if (statusCode >= 200 && statusCode < 400) {
+ throttler.decrementFailureCountAndResetSkips();
+ }
+ if (statusCode >= 400) {
+ throttler.handleHttpErrorCodes(statusCode);
+ }
+ } catch (YggdrasilError e) {
+ LOGGER.error(
+ "Failed to retrieve metrics from the engine, this is a serious error, please report it",
+ e);
}
} else {
throttler.skipped();
diff --git a/src/main/java/io/getunleash/repository/FeatureRepository.java b/src/main/java/io/getunleash/repository/FeatureRepository.java
index dfeb8a61a..77bce48ae 100644
--- a/src/main/java/io/getunleash/repository/FeatureRepository.java
+++ b/src/main/java/io/getunleash/repository/FeatureRepository.java
@@ -10,6 +10,7 @@
import io.getunleash.util.UnleashConfig;
import io.getunleash.util.UnleashScheduledExecutor;
import java.util.Collections;
+import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -24,6 +25,8 @@ public class FeatureRepository implements IFeatureRepository {
private final FeatureFetcher featureFetcher;
private final EventDispatcher eventDispatcher;
+ private List> consumers = new LinkedList<>();
+
private final Throttler throttler;
private FeatureCollection featureCollection;
@@ -116,6 +119,10 @@ private void initCollections(UnleashScheduledExecutor executor) {
}
}
+ public void addConsumer(Consumer consumer) {
+ this.consumers.add(consumer);
+ }
+
private Runnable updateFeatures(final Consumer handler) {
return () -> {
if (throttler.performAction()) {
@@ -131,6 +138,14 @@ private Runnable updateFeatures(final Consumer handler) {
? segmentCollection
: new SegmentCollection(Collections.emptyList()));
+ consumers.forEach(
+ consumer -> {
+ try {
+ consumer.accept(featureCollection);
+ } catch (Exception e) {
+ LOGGER.error("Error when calling consumer {}", consumer, e);
+ }
+ });
featureBackupHandler.write(featureCollection);
} else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) {
if (!ready && unleashConfig.isSynchronousFetchOnInitialisation()) {
diff --git a/src/main/java/io/getunleash/repository/IFeatureRepository.java b/src/main/java/io/getunleash/repository/IFeatureRepository.java
index 635f612da..a15771713 100644
--- a/src/main/java/io/getunleash/repository/IFeatureRepository.java
+++ b/src/main/java/io/getunleash/repository/IFeatureRepository.java
@@ -2,8 +2,11 @@
import io.getunleash.Segment;
import io.getunleash.lang.Nullable;
+import java.util.function.Consumer;
public interface IFeatureRepository extends ToggleRepository {
@Nullable
Segment getSegment(Integer id);
+
+ void addConsumer(Consumer consumer);
}
diff --git a/src/main/java/io/getunleash/repository/JsonFeatureParser.java b/src/main/java/io/getunleash/repository/JsonFeatureParser.java
index 4ff1efc66..5d1f83d44 100644
--- a/src/main/java/io/getunleash/repository/JsonFeatureParser.java
+++ b/src/main/java/io/getunleash/repository/JsonFeatureParser.java
@@ -4,7 +4,7 @@
import com.google.gson.GsonBuilder;
import java.io.Reader;
-final class JsonFeatureParser {
+public final class JsonFeatureParser {
private JsonFeatureParser() {}
diff --git a/src/main/java/io/getunleash/util/InstantSerializer.java b/src/main/java/io/getunleash/util/InstantSerializer.java
new file mode 100644
index 000000000..9491387cb
--- /dev/null
+++ b/src/main/java/io/getunleash/util/InstantSerializer.java
@@ -0,0 +1,20 @@
+package io.getunleash.util;
+
+import com.google.gson.*;
+import java.lang.reflect.Type;
+import java.time.Instant;
+
+public class InstantSerializer implements JsonSerializer, JsonDeserializer {
+
+ @Override
+ public JsonElement serialize(
+ Instant instant, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(instant.toString());
+ }
+
+ @Override
+ public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return Instant.parse(json.getAsString());
+ }
+}
diff --git a/src/main/java/io/getunleash/variant/VariantDefinition.java b/src/main/java/io/getunleash/variant/VariantDefinition.java
index 74409933c..ea59a53c9 100644
--- a/src/main/java/io/getunleash/variant/VariantDefinition.java
+++ b/src/main/java/io/getunleash/variant/VariantDefinition.java
@@ -63,6 +63,6 @@ List getOverrides() {
}
Variant toVariant() {
- return new Variant(name, payload, true, stickiness);
+ return new Variant(name, payload, true, stickiness, false);
}
}
diff --git a/src/test/java/io/getunleash/DefaultUnleashTest.java b/src/test/java/io/getunleash/DefaultUnleashTest.java
index beebaa196..5344bf540 100644
--- a/src/test/java/io/getunleash/DefaultUnleashTest.java
+++ b/src/test/java/io/getunleash/DefaultUnleashTest.java
@@ -52,7 +52,6 @@ public void setup() {
strategyMap.put("default", new DefaultStrategy());
contextProvider = mock(UnleashContextProvider.class);
eventDispatcher = mock(EventDispatcher.class);
- metricService = mock(UnleashMetricService.class);
sut =
new DefaultUnleash(
@@ -60,8 +59,7 @@ public void setup() {
featureRepository,
strategyMap,
contextProvider,
- eventDispatcher,
- metricService);
+ eventDispatcher);
}
@Test
@@ -76,22 +74,6 @@ public void should_evaluate_all_toggle_with_context() {
assertThat(t1.isEnabled()).isFalse();
}
- @Test
- public void should_count_and_not_throw_an_error() {
- sut.more().count("toggle1", true);
- sut.more().count("toggle1", false);
-
- verify(metricService).count("toggle1", true);
- verify(metricService).count("toggle1", false);
- }
-
- @Test
- public void should_countVariant_and_not_throw_an_error() {
- sut.more().countVariant("toggle1", "variant1");
-
- verify(metricService).countVariant("toggle1", "variant1");
- }
-
@Test
public void should_evaluate_missing_segment_as_false() {
String toggleName = "F9.withMissingSegment";
@@ -104,9 +86,12 @@ public void should_evaluate_missing_segment_as_false() {
asList(semverConstraint),
asList(404),
Collections.emptyList());
- when(featureRepository.getToggle(toggleName))
- .thenReturn(new FeatureToggle(toggleName, true, asList(withMissingSegment)));
- when(featureRepository.getSegment(404)).thenReturn(Segment.DENY_SEGMENT);
+ new UnleashEngineStateHandler(sut)
+ .setState(
+ Collections.singletonList(
+ new FeatureToggle(toggleName, true, asList(withMissingSegment))),
+ Collections.singletonList(Segment.DENY_SEGMENT));
+
when(contextProvider.getContext())
.thenReturn(UnleashContext.builder().addProperty("version", semVer).build());
assertThat(sut.isEnabled(toggleName)).isFalse();
@@ -123,18 +108,20 @@ public void should_evaluate_segment_collection_with_one_missing_segment_as_false
asList(semverConstraint),
asList(404, 1),
Collections.emptyList());
- when(featureRepository.getToggle(toggleName))
- .thenReturn(new FeatureToggle(toggleName, true, asList(withMissingSegment)));
- when(featureRepository.getSegment(1))
- .thenReturn(
- new Segment(
- 1,
- "always true",
- asList(
- new Constraint(
- "always_true",
- Operator.NOT_IN,
- Collections.EMPTY_LIST))));
+ new UnleashEngineStateHandler(sut)
+ .setState(
+ Collections.singletonList(
+ new FeatureToggle(toggleName, true, asList(withMissingSegment))),
+ Collections.singletonList(
+ new Segment(
+ 1,
+ "always true",
+ asList(
+ new Constraint(
+ "always_true",
+ Operator.NOT_IN,
+ Collections.EMPTY_LIST)))));
+
when(contextProvider.getContext())
.thenReturn(UnleashContext.builder().addProperty("version", "1.2.2").build());
assertThat(sut.isEnabled(toggleName)).isFalse();
@@ -157,17 +144,16 @@ public void should_allow_fallback_strategy() {
featureRepository,
new HashMap<>(),
contextProvider,
- eventDispatcher,
- metricService);
+ eventDispatcher);
ActivationStrategy as = new ActivationStrategy("forFallback", new HashMap<>());
FeatureToggle toggle = new FeatureToggle("toggle1", true, Collections.singletonList(as));
- when(featureRepository.getToggle("toggle1")).thenReturn(toggle);
+ new UnleashEngineStateHandler(sut).setState(toggle);
when(contextProvider.getContext()).thenReturn(UnleashContext.builder().build());
sut.isEnabled("toggle1");
- verify(fallback).isEnabled(any(), any(), anyList());
+ verify(fallback).isEnabled(any(), any());
}
@Test
@@ -228,7 +214,7 @@ public void supports_failing_hard_on_multiple_instantiations() {
assertThatThrownBy(
() -> {
Unleash unleash2 =
- new DefaultUnleash(config, null, null, null, null, null, true);
+ new DefaultUnleash(config, null, null, null, null, true);
})
.isInstanceOf(RuntimeException.class)
.withFailMessage(
diff --git a/src/test/java/io/getunleash/DependentFeatureToggleTest.java b/src/test/java/io/getunleash/DependentFeatureToggleTest.java
deleted file mode 100644
index 194bf671f..000000000
--- a/src/test/java/io/getunleash/DependentFeatureToggleTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-package io.getunleash;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-import io.getunleash.event.EventDispatcher;
-import io.getunleash.event.IsEnabledImpressionEvent;
-import io.getunleash.event.VariantImpressionEvent;
-import io.getunleash.metric.UnleashMetricService;
-import io.getunleash.repository.FeatureRepository;
-import io.getunleash.strategy.DefaultStrategy;
-import io.getunleash.strategy.Strategy;
-import io.getunleash.util.UnleashConfig;
-import io.getunleash.variant.VariantDefinition;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class DependentFeatureToggleTest {
- private DefaultUnleash sut;
- private FeatureRepository featureRepository;
- private UnleashContextProvider contextProvider;
- private EventDispatcher eventDispatcher;
- private UnleashMetricService metricService;
-
- @BeforeEach
- public void setup() {
- UnleashConfig unleashConfig =
- UnleashConfig.builder().unleashAPI("http://fakeAPI").appName("fakeApp").build();
- featureRepository = mock(FeatureRepository.class);
- Map strategyMap = new HashMap<>();
- strategyMap.put("default", new DefaultStrategy());
- contextProvider = mock(UnleashContextProvider.class);
- eventDispatcher = mock(EventDispatcher.class);
- metricService = mock(UnleashMetricService.class);
-
- sut =
- new DefaultUnleash(
- unleashConfig,
- featureRepository,
- strategyMap,
- contextProvider,
- eventDispatcher,
- metricService);
- }
-
- @Test
- public void should_not_increment_count_for_parent_toggle_when_checking_child_toggle() {
- FeatureToggle child =
- new FeatureToggle(
- "child",
- true,
- singletonList(new ActivationStrategy("default", null)),
- Collections.emptyList(),
- false,
- singletonList(new FeatureDependency("parent")));
- FeatureToggle parent =
- new FeatureToggle(
- "parent",
- true,
- singletonList(new ActivationStrategy("default", null)),
- Collections.emptyList(),
- false);
- when(featureRepository.getToggle("child")).thenReturn(child);
- when(featureRepository.getToggle("parent")).thenReturn(parent);
- boolean enabled = sut.isEnabled("child", UnleashContext.builder().userId("7").build());
- assertThat(enabled).isTrue();
- verify(metricService).count("child", true);
- verify(metricService, never()).count(eq("parent"), anyBoolean());
- }
-
- @Test
- public void should_not_increment_count_for_parent_toggle_when_checking_parent_variants() {
- FeatureToggle child =
- new FeatureToggle(
- "child",
- true,
- singletonList(new ActivationStrategy("default", null)),
- singletonList(new VariantDefinition("childVariant", 1, null, null)),
- false,
- singletonList(
- new FeatureDependency("parent", true, singletonList("first"))));
- FeatureToggle parent =
- new FeatureToggle(
- "parent",
- true,
- singletonList(new ActivationStrategy("default", null)),
- singletonList(new VariantDefinition("first", 1, null, null, null)),
- false);
- when(featureRepository.getToggle("child")).thenReturn(child);
- when(featureRepository.getToggle("parent")).thenReturn(parent);
- Variant variant = sut.getVariant("child", UnleashContext.builder().userId("7").build());
- assertThat(variant).isNotNull();
- verify(metricService).countVariant("child", "childVariant");
- verify(metricService, never()).countVariant("parent", "first");
- }
-
- @Test
- public void should_trigger_impression_event_for_parent_toggle_when_checking_child_toggle() {
- FeatureToggle child =
- new FeatureToggle(
- "child",
- true,
- singletonList(new ActivationStrategy("default", null)),
- Collections.emptyList(),
- false,
- singletonList(new FeatureDependency("parent")));
- FeatureToggle parent =
- new FeatureToggle(
- "parent",
- true,
- singletonList(new ActivationStrategy("default", null)),
- Collections.emptyList(),
- true);
- when(featureRepository.getToggle("child")).thenReturn(child);
- when(featureRepository.getToggle("parent")).thenReturn(parent);
- boolean enabled = sut.isEnabled("child", UnleashContext.builder().userId("7").build());
- assertThat(enabled).isTrue();
- verify(eventDispatcher).dispatch(any(IsEnabledImpressionEvent.class));
- }
-
- @Test
- public void should_trigger_impression_event_for_parent_variant_when_checking_child_toggle() {
- FeatureToggle child =
- new FeatureToggle(
- "child",
- true,
- singletonList(new ActivationStrategy("default", null)),
- singletonList(new VariantDefinition("childVariant", 1, null, null)),
- true,
- singletonList(
- new FeatureDependency("parent", true, singletonList("first"))));
- FeatureToggle parent =
- new FeatureToggle(
- "parent",
- true,
- singletonList(new ActivationStrategy("default", null)),
- singletonList(new VariantDefinition("first", 1, null, null, null)),
- true);
- when(featureRepository.getToggle("child")).thenReturn(child);
- when(featureRepository.getToggle("parent")).thenReturn(parent);
- Variant variant = sut.getVariant("child", UnleashContext.builder().userId("7").build());
- assertThat(variant).isNotNull();
- verify(eventDispatcher, times(2)).dispatch(any(VariantImpressionEvent.class));
- }
-
- @Test
- public void
- child_is_disabled_if_the_parent_is_disabled_even_if_the_childs_expected_variant_is_the_disabled_variant() {
- Map parameters = new HashMap<>();
- parameters.put("rollout", "100");
- parameters.put("stickiness", "default");
- parameters.put("groupId", "groupId");
- String parentName = "parent.disabled";
- FeatureToggle parent =
- new FeatureToggle(
- parentName,
- false,
- singletonList(new ActivationStrategy("default", new HashMap<>())));
- String childName = "parent.disabled.child.expects.disabled.variant";
- FeatureToggle child =
- new FeatureToggle(
- childName,
- true,
- singletonList(new ActivationStrategy("flexibleRollout", parameters)),
- emptyList(),
- false,
- singletonList(
- new FeatureDependency(
- parentName, null, singletonList("disabled"))));
- when(featureRepository.getToggle(childName)).thenReturn(child);
- when(featureRepository.getToggle(parentName)).thenReturn(parent);
- assertThat(sut.isEnabled(childName, UnleashContext.builder().build())).isFalse();
- }
-
- @Test
- public void childIsDisabledWhenChildDoesNotHaveStrategiesAndParentIsDisabled() {
- FeatureToggle parent =
- new FeatureToggle(
- "parent", false, singletonList(new ActivationStrategy("default", null)));
- FeatureDependency childDependsOnParent = new FeatureDependency("parant", true, emptyList());
- FeatureToggle child =
- new FeatureToggle(
- "child",
- true,
- emptyList(),
- emptyList(),
- true,
- singletonList(childDependsOnParent));
- when(featureRepository.getToggle("child")).thenReturn(child);
- when(featureRepository.getToggle("parent")).thenReturn(parent);
- assertThat(sut.isEnabled("child", UnleashContext.builder().build())).isFalse();
- }
-
- @Test
- public void shouldBeEnabledWhenMissingStrategies() {
- FeatureToggle c = new FeatureToggle("c", true, emptyList());
- when(featureRepository.getToggle("c")).thenReturn(c);
- assertThat(sut.isEnabled("c", UnleashContext.builder().build())).isTrue();
- }
-}
diff --git a/src/test/java/io/getunleash/FakeUnleashTest.java b/src/test/java/io/getunleash/FakeUnleashTest.java
index daaaf09c2..bee2eceeb 100644
--- a/src/test/java/io/getunleash/FakeUnleashTest.java
+++ b/src/test/java/io/getunleash/FakeUnleashTest.java
@@ -168,18 +168,6 @@ public void should_get_disabled_variant_when_toggle_is_disabled() {
assertThat(fakeUnleash.getVariant("t1").getName()).isEqualTo("disabled");
}
- @Test
- public void should_count_and_not_throw_an_error() {
- FakeUnleash fakeUnleash = new FakeUnleash();
- fakeUnleash.more().count("anything", true);
- }
-
- @Test
- public void should_countVariant_and_not_throw_an_error() {
- FakeUnleash fakeUnleash = new FakeUnleash();
- fakeUnleash.more().countVariant("toggleName", "variantName");
- }
-
@Test
public void
if_all_is_enabled_should_return_true_even_if_feature_does_not_exist_and_fallback_returns_false() {
diff --git a/src/test/java/io/getunleash/UnleashTest.java b/src/test/java/io/getunleash/UnleashTest.java
index f80c976bc..2db7c0914 100644
--- a/src/test/java/io/getunleash/UnleashTest.java
+++ b/src/test/java/io/getunleash/UnleashTest.java
@@ -6,9 +6,10 @@
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
+import io.getunleash.engine.MetricsBucket;
import io.getunleash.event.EventDispatcher;
-import io.getunleash.metric.UnleashMetricService;
import io.getunleash.repository.*;
+import io.getunleash.repository.UnleashEngineStateHandler;
import io.getunleash.strategy.Strategy;
import io.getunleash.strategy.UserWithIdStrategy;
import io.getunleash.util.UnleashConfig;
@@ -26,6 +27,8 @@ public class UnleashTest {
private UnleashContextProvider contextProvider;
private Unleash unleash;
+ private UnleashEngineStateHandler stateHandler;
+
@BeforeEach
public void setup() {
toggleRepository = mock(FeatureRepository.class);
@@ -43,15 +46,13 @@ public void setup() {
.build();
unleash = new DefaultUnleash(config, toggleRepository, new UserWithIdStrategy());
+ stateHandler = new UnleashEngineStateHandler((DefaultUnleash) unleash);
}
@Test
public void known_toogle_and_strategy_should_be_active() {
- when(toggleRepository.getToggle("test"))
- .thenReturn(
- new FeatureToggle(
- "test", true, asList(new ActivationStrategy("default", null))));
-
+ stateHandler.setState(
+ new FeatureToggle("test", true, asList(new ActivationStrategy("default", null))));
assertThat(unleash.isEnabled("test")).isTrue();
}
@@ -113,10 +114,8 @@ void fallback_function_should_be_invoked_and_return_false() {
@Test
void fallback_function_should_not_be_called_when_toggle_is_defined() {
- when(toggleRepository.getToggle("test"))
- .thenReturn(
- new FeatureToggle(
- "test", true, asList(new ActivationStrategy("default", null))));
+ stateHandler.setState(
+ new FeatureToggle("test", true, asList(new ActivationStrategy("default", null))));
BiPredicate fallbackAction = mock(BiPredicate.class);
when(fallbackAction.test(eq("test"), any(UnleashContext.class))).thenReturn(false);
@@ -139,14 +138,13 @@ public void should_register_custom_strategies() {
.unleashAPI("http://localhost:4242/api/")
.build();
unleash = new DefaultUnleash(config, toggleRepository, customStrategy);
- when(toggleRepository.getToggle("test"))
- .thenReturn(
+ new UnleashEngineStateHandler((DefaultUnleash) unleash)
+ .setState(
new FeatureToggle(
"test", true, asList(new ActivationStrategy("custom", null))));
-
unleash.isEnabled("test");
- verify(customStrategy, times(1)).isEnabled(any(), any(UnleashContext.class), anyList());
+ verify(customStrategy, times(1)).isEnabled(any(), any(UnleashContext.class));
}
@Test
@@ -157,7 +155,7 @@ public void should_support_multiple_strategies() {
FeatureToggle featureToggle =
new FeatureToggle("test", true, asList(strategy1, activeStrategy));
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
assertThat(unleash.isEnabled("test")).isTrue();
}
@@ -189,7 +187,7 @@ public void shouldSupportMultipleRolloutStrategies() {
FeatureToggle featureToggle = new FeatureToggle("test", true, asList(strategy1, strategy2));
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
assertThat(unleash.isEnabled("test", UnleashContext.builder().userId("1").build()))
.isFalse();
@@ -212,7 +210,7 @@ public void should_support_context_provider() {
ActivationStrategy strategy = new ActivationStrategy("userWithId", params);
FeatureToggle featureToggle = new FeatureToggle("test", true, asList(strategy));
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
assertThat(unleash.isEnabled("test")).isTrue();
}
@@ -227,7 +225,7 @@ public void should_support_context_as_part_of_is_enabled_call() {
ActivationStrategy strategy = new ActivationStrategy("userWithId", params);
FeatureToggle featureToggle = new FeatureToggle("test", true, asList(strategy));
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
assertThat(unleash.isEnabled("test", context)).isTrue();
}
@@ -302,7 +300,6 @@ public void get_default_variant_when_disabled() {
@Test
public void getting_variant_when_disabled_should_increment_no_counter() {
UnleashContext context = UnleashContext.builder().userId("1").build();
- UnleashMetricService metricService = mock(UnleashMetricService.class);
UnleashConfig config =
new UnleashConfig.Builder()
.appName("test")
@@ -317,21 +314,23 @@ public void getting_variant_when_disabled_should_increment_no_counter() {
toggleRepository,
Collections.emptyMap(),
contextProvider,
- new EventDispatcher(config),
- metricService);
+ new EventDispatcher(config));
+ UnleashEngineStateHandler localStateHandler =
+ new UnleashEngineStateHandler((DefaultUnleash) thisUnleash);
// Set up a toggleName using UserWithIdStrategy
Map params = new HashMap<>();
params.put("userIds", "123, 111, 121, 13");
ActivationStrategy strategy = new ActivationStrategy("userWithId", params);
FeatureToggle featureToggle = new FeatureToggle("test", false, asList(strategy));
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ localStateHandler.setState(featureToggle);
final Variant result = thisUnleash.getVariant("test", context);
assertThat(result).isNotNull();
- verify(metricService).count(anyString(), eq(false));
- verify(metricService).countVariant(anyString(), eq(result.getName()));
+ MetricsBucket bucket = localStateHandler.captureMetrics();
+ assertThat(bucket.getToggles().get("test").getYes()).isEqualTo(0);
+ assertThat(bucket.getToggles().get("test").getNo()).isEqualTo(1);
}
@Test
@@ -365,7 +364,7 @@ public void get_first_variant() {
FeatureToggle featureToggle =
new FeatureToggle("test", true, asList(strategy), getTestVariants());
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
final Variant result = unleash.getVariant("test", context);
@@ -386,7 +385,7 @@ public void get_second_variant() {
FeatureToggle featureToggle =
new FeatureToggle("test", true, asList(strategy), getTestVariants());
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
final Variant result = unleash.getVariant("test", context);
@@ -444,7 +443,7 @@ public void get_first_variant_with_context_provider() {
FeatureToggle featureToggle =
new FeatureToggle("test", true, asList(strategy), getTestVariants());
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
final Variant result = unleash.getVariant("test");
@@ -467,7 +466,7 @@ public void get_second_variant_with_context_provider() {
FeatureToggle featureToggle =
new FeatureToggle("test", true, asList(strategy), getTestVariants());
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
final Variant result = unleash.getVariant("test");
@@ -477,36 +476,6 @@ public void get_second_variant_with_context_provider() {
assertThat(result.isEnabled()).isTrue();
}
- @Test
- public void get_second_variant_with_context_provider_and_deprecated_algorithm() {
-
- UnleashContext context = UnleashContext.builder().userId("5").build();
- when(contextProvider.getContext()).thenReturn(context);
-
- // Set up a toggleName using UserWithIdStrategy
- Map params = new HashMap<>();
- params.put("userIds", "123, 5, 121");
- ActivationStrategy strategy = new ActivationStrategy("userWithId", params);
- FeatureToggle featureToggle =
- new FeatureToggle(
- "test", true, asList(strategy), getTestVariantsForDeprecatedHash());
-
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
-
- final Variant result = unleash.deprecatedGetVariant("test");
-
- assertThat(result).isNotNull();
- assertThat(result.getName()).isEqualTo("en");
- assertThat(result.getPayload().map(Payload::getValue).get()).isEqualTo("en");
- assertThat(result.isEnabled()).isTrue();
-
- final Variant newHash = unleash.getVariant("test");
- assertThat(newHash).isNotNull();
- assertThat(newHash.getName()).isEqualTo("to");
- assertThat(newHash.getPayload().map(Payload::getValue).get()).isEqualTo("to");
- assertThat(newHash.isEnabled()).isTrue();
- }
-
@Test
public void should_be_enabled_with_strategy_constraints() {
List constraints = new ArrayList<>();
@@ -521,7 +490,7 @@ public void should_be_enabled_with_strategy_constraints() {
FeatureToggle featureToggle = new FeatureToggle("test", true, asList(activeStrategy));
- when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+ stateHandler.setState(featureToggle);
assertThat(unleash.isEnabled("test")).isTrue();
}
@@ -550,24 +519,19 @@ public void should_handle_complex_segment_chains() {
UnleashConfig config =
UnleashConfig.builder()
.appName("test")
- .unleashAPI("http://http://unleash.org")
+ .unleashAPI("http://unleash.org")
.backupFile(
getClass().getResource("/unleash-repo-v2-advanced.json").getFile())
.build();
FeatureBackupHandlerFile backupHandler = new FeatureBackupHandlerFile(config);
FeatureCollection featureCollection = backupHandler.read();
- when(toggleRepository.getToggle(anyString()))
- .thenReturn(featureCollection.getToggle("Test.variants"));
- when(toggleRepository.getSegment(0)).thenReturn(featureCollection.getSegment(0));
- when(toggleRepository.getSegment(1)).thenReturn(featureCollection.getSegment(1));
- when(toggleRepository.getSegment(2)).thenReturn(featureCollection.getSegment(2));
- when(toggleRepository.getSegment(3)).thenReturn(featureCollection.getSegment(3));
+ stateHandler.setState(featureCollection);
UnleashContext context =
UnleashContext.builder()
.addProperty("wins", "6")
- .addProperty("dateLastWin", "2022-06-01T12:00:00")
+ .addProperty("dateLastWin", "2022-06-01T12:00:00.000Z")
.addProperty("followers", "1500")
.addProperty("single", "true")
.addProperty("catOrDog", "cat")
@@ -589,24 +553,20 @@ public void should_handle_complex_segment_chains_2() {
FeatureBackupHandlerFile backupHandler = new FeatureBackupHandlerFile(config);
FeatureCollection featureCollection = backupHandler.read();
- when(toggleRepository.getToggle(anyString()))
- .thenReturn(featureCollection.getToggle("Test.variants"));
- when(toggleRepository.getSegment(0)).thenReturn(featureCollection.getSegment(0));
- when(toggleRepository.getSegment(1)).thenReturn(featureCollection.getSegment(1));
- when(toggleRepository.getSegment(2)).thenReturn(featureCollection.getSegment(2));
- when(toggleRepository.getSegment(3)).thenReturn(featureCollection.getSegment(3));
+ Unleash overrideUnleash = new DefaultUnleash(config);
+ new UnleashEngineStateHandler((DefaultUnleash) overrideUnleash).setState(featureCollection);
UnleashContext context =
UnleashContext.builder()
.addProperty("wins", "4")
- .addProperty("dateLastWin", "2022-06-01T12:00:00")
+ .addProperty("dateLastWin", "2022-06-01T12:00:00.000Z")
.addProperty("followers", "900")
.addProperty("single", "false")
.addProperty("catOrDog", "dog")
.build();
when(contextProvider.getContext()).thenReturn(context);
- assertThat(unleash.isEnabled("Test.variants")).isFalse();
+ assertThat(overrideUnleash.isEnabled("Test.variants")).isFalse();
}
@Test
@@ -628,6 +588,27 @@ public void should_be_disabled_with_strategy_variants() {
assertThat(unleash.isEnabled("test")).isFalse();
}
+ @Test
+ public void empty_variants_returns_disabled_variant() {
+ UnleashContext context = UnleashContext.builder().build();
+
+ Map params = new HashMap<>();
+ params.put("rollout", "100");
+ params.put("stickiness", "default");
+ params.put("groupId", "test");
+
+ ActivationStrategy strategy = new ActivationStrategy("flexibleRollout", params);
+ FeatureToggle featureToggle =
+ new FeatureToggle("test", true, asList(strategy), Collections.emptyList());
+
+ stateHandler.setState(featureToggle);
+ final Variant result = unleash.getVariant("test", context);
+
+ assertThat(result.getName()).isEqualTo("disabled");
+ assertThat(result.isEnabled()).isFalse();
+ assertThat(result.isFeatureEnabled()).isTrue();
+ }
+
private List getTestVariants() {
return asList(
new VariantDefinition(
diff --git a/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java b/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java
index 48be6d8f8..1cb1a20f8 100644
--- a/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java
+++ b/src/test/java/io/getunleash/event/ImpressionDataSubscriberTest.java
@@ -1,14 +1,12 @@
package io.getunleash.event;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import io.getunleash.DefaultUnleash;
import io.getunleash.FeatureToggle;
import io.getunleash.SynchronousTestExecutor;
import io.getunleash.Unleash;
-import io.getunleash.repository.FeatureRepository;
+import io.getunleash.repository.UnleashEngineStateHandler;
import io.getunleash.util.UnleashConfig;
import io.getunleash.variant.VariantDefinition;
import java.util.ArrayList;
@@ -20,10 +18,9 @@ public class ImpressionDataSubscriberTest {
private ImpressionTestSubscriber testSubscriber = new ImpressionTestSubscriber();
- private FeatureRepository toggleRepository;
-
private UnleashConfig unleashConfig;
+ private UnleashEngineStateHandler stateHandler;
private Unleash unleash;
@BeforeEach
@@ -32,22 +29,19 @@ void setup() {
new UnleashConfig.Builder()
.appName(SubscriberTest.class.getSimpleName())
.instanceId(SubscriberTest.class.getSimpleName())
- .synchronousFetchOnInitialisation(true)
.unleashAPI("http://localhost:4242/api")
.subscriber(testSubscriber)
.scheduledExecutor(new SynchronousTestExecutor())
.build();
- toggleRepository = mock(FeatureRepository.class);
- unleash = new DefaultUnleash(unleashConfig, toggleRepository);
+ unleash = new DefaultUnleash(unleashConfig);
+ stateHandler = new UnleashEngineStateHandler((DefaultUnleash) unleash);
}
@Test
public void noEventsIfImpressionDataIsNotEnabled() {
String featureWithoutImpressionDataEnabled = "feature.with.no.impressionData";
- when(toggleRepository.getToggle(featureWithoutImpressionDataEnabled))
- .thenReturn(
- new FeatureToggle(
- featureWithoutImpressionDataEnabled, true, new ArrayList<>()));
+ stateHandler.setState(
+ new FeatureToggle(featureWithoutImpressionDataEnabled, true, new ArrayList<>()));
unleash.isEnabled(featureWithoutImpressionDataEnabled);
assertThat(testSubscriber.isEnabledImpressions).isEqualTo(0);
assertThat(testSubscriber.variantImpressions).isEqualTo(0);
@@ -56,8 +50,8 @@ public void noEventsIfImpressionDataIsNotEnabled() {
@Test
public void isEnabledEventWhenImpressionDataIsEnabled() {
String featureWithImpressionData = "feature.with.impressionData";
- when(toggleRepository.getToggle(featureWithImpressionData))
- .thenReturn(
+ new UnleashEngineStateHandler((DefaultUnleash) unleash)
+ .setState(
new FeatureToggle(
featureWithImpressionData,
true,
@@ -75,14 +69,9 @@ public void variantEventWhenVariantIsRequested() {
VariantDefinition def = new VariantDefinition("blue", 1000, null, null);
List variants = new ArrayList<>();
variants.add(def);
- when(toggleRepository.getToggle(featureWithImpressionData))
- .thenReturn(
- new FeatureToggle(
- featureWithImpressionData,
- true,
- new ArrayList<>(),
- variants,
- true));
+ stateHandler.setState(
+ new FeatureToggle(
+ featureWithImpressionData, true, new ArrayList<>(), variants, true));
unleash.getVariant(featureWithImpressionData);
assertThat(testSubscriber.isEnabledImpressions).isEqualTo(0);
assertThat(testSubscriber.variantImpressions).isEqualTo(1);
diff --git a/src/test/java/io/getunleash/integration/ClientSpecificationTest.java b/src/test/java/io/getunleash/integration/ClientSpecificationTest.java
index 7a3fc1255..76f1cbff8 100644
--- a/src/test/java/io/getunleash/integration/ClientSpecificationTest.java
+++ b/src/test/java/io/getunleash/integration/ClientSpecificationTest.java
@@ -17,6 +17,7 @@
import io.getunleash.Unleash;
import io.getunleash.UnleashContext;
import io.getunleash.Variant;
+import io.getunleash.repository.UnleashEngineStateHandler;
import io.getunleash.strategy.constraints.DateParser;
import io.getunleash.util.UnleashConfig;
import java.io.BufferedReader;
@@ -112,6 +113,10 @@ private List createVariantTests(String fileName)
test.getExpectedResult().getPayload(),
result.getPayload(),
test.getDescription());
+ assertEquals(
+ test.getExpectedResult().isFeatureEnabled(),
+ result.isFeatureEnabled(),
+ test.getDescription());
}))
.collect(Collectors.toList());
}
@@ -119,7 +124,8 @@ private List createVariantTests(String fileName)
private Unleash setupUnleash(TestDefinition testDefinition) throws URISyntaxException {
mockUnleashAPI(testDefinition);
- // Required because the client is available before it may have had the chance to talk with
+ // Required because the client is available before it may have had the chance to
+ // talk with
// the API
String backupFile = writeUnleashBackup(testDefinition);
@@ -132,7 +138,10 @@ private Unleash setupUnleash(TestDefinition testDefinition) throws URISyntaxExce
.backupFile(backupFile)
.build();
- return new DefaultUnleash(config);
+ DefaultUnleash defaultUnleash = new DefaultUnleash(config);
+ new UnleashEngineStateHandler(defaultUnleash)
+ .setState(testDefinition.getState().toString());
+ return defaultUnleash;
}
private void mockUnleashAPI(TestDefinition definition) {
@@ -190,7 +199,8 @@ private String writeUnleashBackup(TestDefinition definition) {
+ definition.getName()
+ ".json";
- // TODO: we can probably drop this after introduction of `synchronousFetchOnInitialisation`.
+ // TODO: we can probably drop this after introduction of
+ // `synchronousFetchOnInitialisation`.
try (FileWriter writer = new FileWriter(backupFile)) {
writer.write(definition.getState().toString());
} catch (IOException e) {
diff --git a/src/test/java/io/getunleash/integration/TestCaseVariant.java b/src/test/java/io/getunleash/integration/TestCaseVariant.java
index bbb7adaa7..f70a09c39 100644
--- a/src/test/java/io/getunleash/integration/TestCaseVariant.java
+++ b/src/test/java/io/getunleash/integration/TestCaseVariant.java
@@ -22,7 +22,14 @@ public String getToggleName() {
public Variant getExpectedResult() {
if (expectedResult.getName().equals("disabled")) {
- return Variant.DISABLED_VARIANT;
+ Variant clone =
+ new Variant(
+ Variant.DISABLED_VARIANT.getName(),
+ Variant.DISABLED_VARIANT.getPayload().orElse(null),
+ Variant.DISABLED_VARIANT.isEnabled(),
+ Variant.DISABLED_VARIANT.getStickiness(),
+ expectedResult.isFeatureEnabled());
+ return clone;
}
return expectedResult;
diff --git a/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java b/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java
index f4250a99c..7f8be65e8 100644
--- a/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java
+++ b/src/test/java/io/getunleash/metric/DefaultHttpMetricsSenderTest.java
@@ -11,9 +11,11 @@
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import io.getunleash.engine.MetricsBucket;
import io.getunleash.util.UnleashConfig;
import java.net.URI;
import java.net.URISyntaxException;
+import java.time.Instant;
import java.time.LocalDateTime;
import java.util.HashSet;
import org.junit.jupiter.api.Test;
@@ -60,7 +62,7 @@ public void should_send_client_metrics() throws URISyntaxException {
UnleashConfig config = UnleashConfig.builder().appName("test-app").unleashAPI(uri).build();
DefaultHttpMetricsSender sender = new DefaultHttpMetricsSender(config);
- MetricsBucket bucket = new MetricsBucket();
+ MetricsBucket bucket = new MetricsBucket(Instant.now(), Instant.now(), null);
ClientMetrics metrics = new ClientMetrics(config, bucket);
sender.sendMetrics(metrics);
@@ -82,7 +84,7 @@ public void should_handle_service_failure_when_sending_metrics() throws URISynta
UnleashConfig config = UnleashConfig.builder().appName("test-app").unleashAPI(uri).build();
DefaultHttpMetricsSender sender = new DefaultHttpMetricsSender(config);
- MetricsBucket bucket = new MetricsBucket();
+ MetricsBucket bucket = new MetricsBucket(Instant.now(), Instant.now(), null);
ClientMetrics metrics = new ClientMetrics(config, bucket);
sender.sendMetrics(metrics);
diff --git a/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java b/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java
index ed5b631ad..ea501186e 100644
--- a/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java
+++ b/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java
@@ -3,6 +3,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
+import io.getunleash.engine.MetricsBucket;
+import io.getunleash.engine.UnleashEngine;
import io.getunleash.util.UnleashConfig;
import io.getunleash.util.UnleashScheduledExecutor;
import java.time.LocalDateTime;
@@ -23,7 +25,8 @@ public void should_register_future_for_sending_interval_regualry() {
.unleashAPI("http://unleash.com")
.build();
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
- UnleashMetricService unleashMetricService = new UnleashMetricServiceImpl(config, executor);
+ UnleashMetricService unleashMetricService =
+ new UnleashMetricServiceImpl(config, executor, null);
verify(executor, times(1)).setInterval(any(Runnable.class), eq(interval), eq(interval));
}
@@ -42,7 +45,7 @@ public void should_register_client() {
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
UnleashMetricService unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
+ new UnleashMetricServiceImpl(config, sender, executor, null);
Set strategies = new HashSet<>();
strategies.add("default");
strategies.add("custom");
@@ -74,7 +77,7 @@ public void should_register_client_with_env() {
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
UnleashMetricService unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
+ new UnleashMetricServiceImpl(config, sender, executor, null);
Set strategies = new HashSet<>();
strategies.add("default");
strategies.add("custom");
@@ -98,9 +101,10 @@ public void should_send_metrics() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricService unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong());
@@ -121,13 +125,14 @@ public void should_record_and_send_metrics() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricService unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
- unleashMetricService.count("someToggle", true);
- unleashMetricService.count("someToggle", false);
- unleashMetricService.count("someToggle", true);
- unleashMetricService.count("otherToggle", true);
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
+ engine.countToggle("someToggle", true);
+ engine.countToggle("someToggle", false);
+ engine.countToggle("someToggle", true);
+ engine.countToggle("otherToggle", true);
// Call the sendMetricsCallback
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
@@ -162,14 +167,15 @@ public void should_record_and_send_variant_metrics() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricService unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v2");
- unleashMetricService.countVariant("someToggle", "disabled");
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v2");
+ engine.countVariant("someToggle", "disabled");
// Call the sendMetricsCallback
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
@@ -209,14 +215,15 @@ public void should_backoff_when_told_to_by_429_code() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricServiceImpl unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v2");
- unleashMetricService.countVariant("someToggle", "disabled");
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v2");
+ engine.countVariant("someToggle", "disabled");
// Call the sendMetricsCallback
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
@@ -281,14 +288,15 @@ public void server_errors_should_also_incrementally_backoff() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricServiceImpl unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v2");
- unleashMetricService.countVariant("someToggle", "disabled");
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v2");
+ engine.countVariant("someToggle", "disabled");
// Call the sendMetricsCallback
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
@@ -351,14 +359,15 @@ public void failure_to_authenticate_immediately_increases_interval_to_max() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricServiceImpl unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v2");
- unleashMetricService.countVariant("someToggle", "disabled");
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v2");
+ engine.countVariant("someToggle", "disabled");
// Call the sendMetricsCallback
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
@@ -388,14 +397,15 @@ public void url_not_found_immediately_increases_interval_to_max() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricServiceImpl unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v1");
- unleashMetricService.countVariant("someToggle", "v2");
- unleashMetricService.countVariant("someToggle", "disabled");
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v1");
+ engine.countVariant("someToggle", "v2");
+ engine.countVariant("someToggle", "disabled");
// Call the sendMetricsCallback
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
@@ -425,9 +435,10 @@ public void should_add_new_metrics_data_to_bucket() {
UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class);
DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class);
+ UnleashEngine engine = new UnleashEngine();
UnleashMetricService unleashMetricService =
- new UnleashMetricServiceImpl(config, sender, executor);
+ new UnleashMetricServiceImpl(config, sender, executor, engine);
ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class);
verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong());
diff --git a/src/test/java/io/getunleash/repository/UnleashEngineStateHandler.java b/src/test/java/io/getunleash/repository/UnleashEngineStateHandler.java
new file mode 100644
index 000000000..5b7ad6b95
--- /dev/null
+++ b/src/test/java/io/getunleash/repository/UnleashEngineStateHandler.java
@@ -0,0 +1,62 @@
+package io.getunleash.repository;
+
+import io.getunleash.DefaultUnleash;
+import io.getunleash.FeatureToggle;
+import io.getunleash.Segment;
+import io.getunleash.engine.*;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class UnleashEngineStateHandler {
+ private final UnleashEngine unleashEngine;
+
+ public UnleashEngineStateHandler(DefaultUnleash unleash) {
+ // Access the private field
+ Field field = null;
+ try {
+ field = DefaultUnleash.class.getDeclaredField("unleashEngine");
+ field.setAccessible(true); // Bypass the "private" access modifier
+ // Get value
+ unleashEngine = (UnleashEngine) field.get(unleash);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setState(FeatureToggle... featureToggles) {
+ FeatureCollection madeUp =
+ new FeatureCollection(
+ new ToggleCollection(Arrays.asList(featureToggles)),
+ new SegmentCollection(Collections.emptyList()));
+ setState(madeUp);
+ }
+
+ public void setState(List featureToggles, List segments) {
+ FeatureCollection madeUp =
+ new FeatureCollection(
+ new ToggleCollection(featureToggles), new SegmentCollection(segments));
+ setState(madeUp);
+ }
+
+ public void setState(FeatureCollection madeUp) {
+ setState(JsonFeatureParser.toJsonString(madeUp));
+ }
+
+ public MetricsBucket captureMetrics() {
+ try {
+ return this.unleashEngine.getMetrics();
+ } catch (YggdrasilError e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setState(String raw) {
+ try {
+ this.unleashEngine.takeState(raw);
+ } catch (YggdrasilInvalidInputException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/test/java/io/getunleash/strategy/StrategyVariantTest.java b/src/test/java/io/getunleash/strategy/StrategyVariantTest.java
deleted file mode 100644
index 6508c6238..000000000
--- a/src/test/java/io/getunleash/strategy/StrategyVariantTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package io.getunleash.strategy;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import io.getunleash.FeatureEvaluationResult;
-import io.getunleash.UnleashContext;
-import io.getunleash.Variant;
-import io.getunleash.variant.Payload;
-import io.getunleash.variant.VariantDefinition;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import org.junit.jupiter.api.Test;
-
-public class StrategyVariantTest {
-
- @Test
- public void should_inherit_stickiness_from_the_strategy_first_variant() {
- HashMap params = new HashMap<>();
- params.put("rollout", "100");
- params.put("stickiness", "clientId");
- params.put("groupId", "a");
- FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
- VariantDefinition varA =
- new VariantDefinition(
- "variantNameA",
- 1,
- new Payload("string", "variantValueA"),
- Collections.emptyList());
- VariantDefinition varB =
- new VariantDefinition(
- "variantNameB",
- 1,
- new Payload("string", "variantValueB"),
- Collections.emptyList());
-
- UnleashContext context = UnleashContext.builder().addProperty("clientId", "1").build();
- FeatureEvaluationResult result =
- strategy.getResult(
- params, context, Collections.emptyList(), Arrays.asList(varA, varB));
- Variant selectedVariant = result.getVariant();
- assert selectedVariant != null;
- assertThat(selectedVariant.getName()).isEqualTo("variantNameA");
- }
-
- @Test
- public void should_inherit_stickiness_from_the_strategy_second_variant() {
- HashMap params = new HashMap<>();
- params.put("rollout", "100");
- params.put("stickiness", "clientId");
- params.put("groupId", "a");
- FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
- VariantDefinition varA =
- new VariantDefinition(
- "variantNameA",
- 1,
- new Payload("string", "variantValueA"),
- Collections.emptyList());
- VariantDefinition varB =
- new VariantDefinition(
- "variantNameB",
- 1,
- new Payload("string", "variantValueB"),
- Collections.emptyList());
-
- UnleashContext context = UnleashContext.builder().addProperty("clientId", "2").build();
- FeatureEvaluationResult result =
- strategy.getResult(
- params, context, Collections.emptyList(), Arrays.asList(varA, varB));
- Variant selectedVariant = result.getVariant();
- assert selectedVariant != null;
- assertThat(selectedVariant.getName()).isEqualTo("variantNameB");
- }
-
- @Test
- public void multiple_variants_should_choose_first_variant() {
- HashMap params = new HashMap<>();
- params.put("rollout", "100");
- params.put("groupId", "a");
- params.put("stickiness", "default");
- FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
- VariantDefinition varA =
- new VariantDefinition(
- "variantNameA",
- 1,
- new Payload("string", "variantValueA"),
- Collections.emptyList());
- VariantDefinition varB =
- new VariantDefinition(
- "variantNameB",
- 1,
- new Payload("string", "variantValueB"),
- Collections.emptyList());
- UnleashContext context = UnleashContext.builder().userId("5").build();
- FeatureEvaluationResult result =
- strategy.getResult(
- params, context, Collections.emptyList(), Arrays.asList(varA, varB));
- Variant selectedVariant = result.getVariant();
- assertThat(selectedVariant.getName()).isEqualTo("variantNameA");
- }
-
- @Test
- public void multiple_variants_should_choose_second_variant() {
- HashMap params = new HashMap<>();
- params.put("rollout", "100");
- params.put("groupId", "a");
- params.put("stickiness", "default");
- FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
- VariantDefinition varA =
- new VariantDefinition(
- "variantNameA",
- 1,
- new Payload("string", "variantValueA"),
- Collections.emptyList());
- VariantDefinition varB =
- new VariantDefinition(
- "variantNameB",
- 1,
- new Payload("string", "variantValueB"),
- Collections.emptyList());
- UnleashContext context = UnleashContext.builder().userId("0").build();
- FeatureEvaluationResult result =
- strategy.getResult(
- params, context, Collections.emptyList(), Arrays.asList(varA, varB));
- Variant selectedVariant = result.getVariant();
- assertThat(selectedVariant.getName()).isEqualTo("variantNameB");
- }
-}
diff --git a/src/test/java/io/getunleash/variant/VariantUtilTest.java b/src/test/java/io/getunleash/variant/VariantUtilTest.java
deleted file mode 100644
index 16325169f..000000000
--- a/src/test/java/io/getunleash/variant/VariantUtilTest.java
+++ /dev/null
@@ -1,441 +0,0 @@
-package io.getunleash.variant;
-
-import static io.getunleash.Variant.DISABLED_VARIANT;
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-import io.getunleash.ActivationStrategy;
-import io.getunleash.FeatureToggle;
-import io.getunleash.UnleashContext;
-import io.getunleash.Variant;
-import io.getunleash.util.UnleashConfig;
-import io.getunleash.util.UnleashScheduledExecutor;
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class VariantUtilTest {
-
- private ActivationStrategy defaultStrategy;
-
- @BeforeEach
- void setUp() {
- UnleashConfig defaultConfig =
- new UnleashConfig.Builder()
- .appName("test")
- .unleashAPI("http://localhost:4242/api/")
- .scheduledExecutor(mock(UnleashScheduledExecutor.class))
- .fetchTogglesInterval(200L)
- .synchronousFetchOnInitialisation(true)
- .build();
-
- defaultStrategy = new ActivationStrategy("default", Collections.emptyMap());
- }
-
- @Test
- public void should_return_default_variant_when_toggle_has_no_variants() {
- FeatureToggle toggle = new FeatureToggle("test.variants", true, asList(defaultStrategy));
- UnleashContext context = UnleashContext.builder().build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant).isEqualTo(DISABLED_VARIANT);
- }
-
- @Test
- public void should_return_variant1() {
- VariantDefinition v1 =
- new VariantDefinition(
- "a", 33, new Payload("string", "asd"), Collections.emptyList());
- VariantDefinition v2 = new VariantDefinition("b", 33);
- VariantDefinition v3 = new VariantDefinition("c", 34);
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context = UnleashContext.builder().userId("11").build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v1.getName());
- assertThat(variant.getPayload()).hasValue(v1.getPayload());
- assertThat(variant.isEnabled()).isTrue();
- }
-
- @Test
- public void should_return_variant2() {
- VariantDefinition v1 =
- new VariantDefinition(
- "a", 33, new Payload("string", "asd"), Collections.emptyList());
- VariantDefinition v2 = new VariantDefinition("b", 33);
- VariantDefinition v3 = new VariantDefinition("c", 34);
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context = UnleashContext.builder().userId("80").build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v2.getName());
- }
-
- @Test
- public void should_return_variant3() {
- VariantDefinition v1 = new VariantDefinition("a", 33);
- VariantDefinition v2 = new VariantDefinition("b", 33);
- VariantDefinition v3 = new VariantDefinition("c", 34);
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context = UnleashContext.builder().userId("163").build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v3.getName());
- }
-
- @Test
- public void should_return_variant_override() {
- VariantDefinition v1 = new VariantDefinition("a", 33);
- VariantOverride override = new VariantOverride("userId", asList("11", "12", "123", "44"));
- VariantDefinition v2 = new VariantDefinition("b", 33, null, asList(override));
- VariantDefinition v3 = new VariantDefinition("c", 34);
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context = UnleashContext.builder().userId("123").build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v2.getName());
- }
-
- @Test
- public void should_return_variant_override_on_remote_adr() {
- VariantDefinition v1 =
- new VariantDefinition(
- "a", 33, new Payload("string", "asd"), Collections.emptyList());
- VariantDefinition v2 = new VariantDefinition("b", 33, null, Collections.emptyList());
- VariantOverride override = new VariantOverride("remoteAddress", asList("11.11.11.11"));
- VariantDefinition v3 =
- new VariantDefinition("c", 34, new Payload("string", "blob"), asList(override));
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context = UnleashContext.builder().remoteAddress("11.11.11.11").build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v3.getName());
- assertThat(variant.getPayload()).hasValue(v3.getPayload());
- assertThat(variant.isEnabled()).isTrue();
- }
-
- @Test
- public void should_return_variant_override_on_custom_prop() {
- VariantDefinition v1 = new VariantDefinition("a", 33);
- VariantOverride override = new VariantOverride("env", asList("ci", "local", "dev"));
- VariantDefinition v2 = new VariantDefinition("b", 33, null, asList(override));
- VariantDefinition v3 = new VariantDefinition("c", 34);
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context =
- UnleashContext.builder().userId("11").addProperty("env", "dev").build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v2.getName());
- }
-
- @Test
- public void should_return_variant_override_on_sessionId() {
- String sessionId = "122221";
-
- VariantDefinition v1 = new VariantDefinition("a", 33);
- VariantOverride override_env = new VariantOverride("env", asList("dev"));
- VariantOverride override_session = new VariantOverride("sessionId", asList(sessionId));
- VariantDefinition v2 =
- new VariantDefinition("b", 33, null, asList(override_env, override_session));
- VariantDefinition v3 = new VariantDefinition("c", 34);
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context =
- UnleashContext.builder()
- .userId("11")
- .addProperty("env", "prod")
- .sessionId(sessionId)
- .build();
-
- Variant variant = VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT);
-
- assertThat(variant.getName()).isEqualTo(v2.getName());
- }
-
- @Test
- public void should_distribute_variants_according_to_stickiness() {
- VariantDefinition v1 = new VariantDefinition("blue", 1, null, null, "customField");
- VariantDefinition v2 = new VariantDefinition("red", 1, null, null, "customField");
- VariantDefinition v3 = new VariantDefinition("green", 1, null, null, "customField");
- VariantDefinition v4 = new VariantDefinition("yellow", 1, null, null, "customField");
- FeatureToggle variantToggle =
- new FeatureToggle(
- "toggle-with-variants",
- true,
- asList(defaultStrategy),
- asList(v1, v2, v3, v4));
- UnleashContext context =
- UnleashContext.builder()
- .userId("11")
- .addProperty("env", "prod")
- .sessionId("1222221")
- .build();
- Map> variantResults =
- IntStream.range(0, 10000)
- .mapToObj(
- i ->
- VariantUtil.selectVariant(
- variantToggle, context, DISABLED_VARIANT))
- .collect(Collectors.groupingBy(Variant::getName));
- assertThat(variantResults)
- .allSatisfy(
- (name, variantResult) ->
- assertThat(variantResult).hasSizeBetween(2300, 2700));
- }
-
- @Test
- public void should_return_same_variant_when_stickiness_is_set_to_default() {
- VariantDefinition v1 = new VariantDefinition("a", 33, null, null, "default");
- VariantDefinition v2 = new VariantDefinition("b", 33, null, null, "default");
- VariantDefinition v3 = new VariantDefinition("c", 34, null, null, "default");
-
- FeatureToggle toggle =
- new FeatureToggle(
- "test.variants", true, asList(defaultStrategy), asList(v1, v2, v3));
-
- UnleashContext context = UnleashContext.builder().userId("163;").build();
- List results =
- IntStream.range(0, 500)
- .mapToObj(i -> VariantUtil.selectVariant(toggle, context, DISABLED_VARIANT))
- .collect(Collectors.toList());
- assertThat(results)
- .allSatisfy(
- (Consumer)
- variant -> assertThat(variant.getName()).isEqualTo(v3.getName()));
- }
-
- @Test
- public void custom_stickiness_variants() {
- Map parameters = new HashMap<>();
- parameters.put("rollout", "100");
- parameters.put("stickiness", "customField");
- parameters.put("groupId", "Feature.flexible.rollout.custom.stickiness_100");
- ActivationStrategy flexibleRollout = new ActivationStrategy("flexibleRollout", parameters);
- List variants = new ArrayList<>();
- variants.add(
- new VariantDefinition(
- "blue",
- 25,
- new Payload("string", "val1"),
- Collections.emptyList(),
- "customField"));
- variants.add(
- new VariantDefinition(
- "red",
- 25,
- new Payload("string", "val1"),
- Collections.emptyList(),
- "customField"));
- variants.add(
- new VariantDefinition(
- "green",
- 25,
- new Payload("string", "val1"),
- Collections.emptyList(),
- "customField"));
- variants.add(
- new VariantDefinition(
- "yellow",
- 25,
- new Payload("string", "val1"),
- Collections.emptyList(),
- "customField"));
- FeatureToggle toggle =
- new FeatureToggle(
- "Feature.flexible.rollout.custom.stickiness_100",
- true,
- asList(flexibleRollout),
- variants);
- Variant variantCustom616 =
- VariantUtil.selectVariant(
- toggle,
- UnleashContext.builder().addProperty("customField", "616").build(),
- DISABLED_VARIANT);
- assertThat(variantCustom616.getName()).isEqualTo("blue");
- Variant variantCustom503 =
- VariantUtil.selectVariant(
- toggle,
- UnleashContext.builder().addProperty("customField", "503").build(),
- DISABLED_VARIANT);
- assertThat(variantCustom503.getName()).isEqualTo("red");
- Variant variantCustom438 =
- VariantUtil.selectVariant(
- toggle,
- UnleashContext.builder().addProperty("customField", "438").build(),
- DISABLED_VARIANT);
- assertThat(variantCustom438.getName()).isEqualTo("green");
- Variant variantCustom44 =
- VariantUtil.selectVariant(
- toggle,
- UnleashContext.builder().addProperty("customField", "44").build(),
- DISABLED_VARIANT);
- assertThat(variantCustom44.getName()).isEqualTo("yellow");
- }
-
- @Test
- public void feature_variants_variant_b_client_spec_tests() {
- List variants = new ArrayList<>();
- variants.add(
- new VariantDefinition(
- "variant1", 1, new Payload("string", "val1"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant2", 1, new Payload("string", "val2"), Collections.emptyList()));
- FeatureToggle toggle =
- new FeatureToggle("Feature.Variants.B", true, Collections.emptyList(), variants);
- Variant variantUser2 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("2").build(), DISABLED_VARIANT);
- assertThat(variantUser2.getName()).isEqualTo("variant2");
- Variant variantUser0 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("0").build(), DISABLED_VARIANT);
- assertThat(variantUser0.getName()).isEqualTo("variant1");
- }
-
- @Test
- public void feature_variants_variant_c_client_spec_tests() {
- List variants = new ArrayList<>();
- variants.add(
- new VariantDefinition(
- "variant1", 33, new Payload("string", "val1"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant2", 33, new Payload("string", "val1"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant3", 33, new Payload("string", "val1"), Collections.emptyList()));
- FeatureToggle toggle =
- new FeatureToggle("Feature.Variants.C", true, Collections.emptyList(), variants);
- Variant variantUser232 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("232").build(), DISABLED_VARIANT);
- assertThat(variantUser232.getName()).isEqualTo("variant1");
- Variant variantUser607 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("607").build(), DISABLED_VARIANT);
- assertThat(variantUser607.getName()).isEqualTo("variant2");
- Variant variantUser656 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("656").build(), DISABLED_VARIANT);
- assertThat(variantUser656.getName()).isEqualTo("variant3");
- }
-
- @Test
- public void feature_variants_variant_d_client_spec_tests() {
- List variants = new ArrayList<>();
- variants.add(
- new VariantDefinition(
- "variant1", 1, new Payload("string", "val1"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant2", 49, new Payload("string", "val2"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant3", 50, new Payload("string", "val3"), Collections.emptyList()));
- FeatureToggle toggle =
- new FeatureToggle("Feature.Variants.D", true, Collections.emptyList(), variants);
- Variant variantUser712 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("712").build(), DISABLED_VARIANT);
- assertThat(variantUser712.getName()).isEqualTo("variant1");
- Variant variantUser525 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("525").build(), DISABLED_VARIANT);
- assertThat(variantUser525.getName()).isEqualTo("variant2");
- Variant variantUser537 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("537").build(), DISABLED_VARIANT);
- assertThat(variantUser537.getName()).isEqualTo("variant3");
- }
-
- @Test
- public void feature_variants_variant_d_client_spec_tests_with_deprecated_seed() {
- List variants = new ArrayList<>();
- variants.add(
- new VariantDefinition(
- "variant1", 1, new Payload("string", "val1"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant2", 49, new Payload("string", "val2"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant3", 50, new Payload("string", "val3"), Collections.emptyList()));
- FeatureToggle toggle =
- new FeatureToggle("Feature.Variants.D", true, Collections.emptyList(), variants);
- Variant variantUser712 =
- VariantUtil.selectDeprecatedVariantHashingAlgo(
- toggle, UnleashContext.builder().userId("712").build(), DISABLED_VARIANT);
- assertThat(variantUser712.getName()).isEqualTo("variant3");
- Variant variantUser525 =
- VariantUtil.selectDeprecatedVariantHashingAlgo(
- toggle, UnleashContext.builder().userId("525").build(), DISABLED_VARIANT);
- assertThat(variantUser525.getName()).isEqualTo("variant3");
- Variant variantUser537 =
- VariantUtil.selectDeprecatedVariantHashingAlgo(
- toggle, UnleashContext.builder().userId("537").build(), DISABLED_VARIANT);
- assertThat(variantUser537.getName()).isEqualTo("variant2");
- }
-
- @Test
- public void feature_variants_variant_d_with_override_client_spec_tests() {
- List variants = new ArrayList<>();
- variants.add(
- new VariantDefinition(
- "variant1",
- 33,
- new Payload("string", "val1"),
- Arrays.asList(new VariantOverride("userId", asList("132", "61")))));
- variants.add(
- new VariantDefinition(
- "variant2", 33, new Payload("string", "val2"), Collections.emptyList()));
- variants.add(
- new VariantDefinition(
- "variant3", 34, new Payload("string", "val3"), Collections.emptyList()));
- FeatureToggle toggle =
- new FeatureToggle(
- "Feature.Variants.override.D", true, Collections.emptyList(), variants);
- Variant variantUser10 =
- VariantUtil.selectVariant(
- toggle, UnleashContext.builder().userId("10").build(), DISABLED_VARIANT);
- assertThat(variantUser10.getName()).isEqualTo("variant2");
- }
-}
diff --git a/src/test/resources/unleash-repo-v2-advanced.json b/src/test/resources/unleash-repo-v2-advanced.json
index 1bd4cf751..4d1ba69d8 100644
--- a/src/test/resources/unleash-repo-v2-advanced.json
+++ b/src/test/resources/unleash-repo-v2-advanced.json
@@ -16,7 +16,7 @@
{
"contextName": "dateLastWin",
"operator": "DATE_AFTER",
- "value": "2022-05-01T12:00:00",
+ "value": "2022-05-01T12:00:00.000Z",
"inverted": false,
"caseInsensitive": true
}