diff --git a/CHANGES.txt b/CHANGES.txt index 4dbbb9ee4..1001aa2e6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +4.18.0 (Sep 12, 2025) +- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. + 4.17.0 (Aug 22, 2025) - Added a maximum size payload when posting unique keys telemetry in batches - Added ProxyConfiguration parameter to support proxies, including Harness Forward Proxy, allowing also for more secured authentication options: MTLS, Bearer token and user/password authentication. Read more in our docs. diff --git a/client/pom.xml b/client/pom.xml index 768f79e9f..4db1af756 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,9 +5,9 @@ io.split.client java-client-parent - 4.17.0 + 4.18.0 - 4.17.0 + 4.18.0 java-client jar Java Client diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index f32b9b091..e6e7a70af 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -1,5 +1,7 @@ package io.split.client; +import io.split.client.dtos.FallbackTreatment; +import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.dtos.ProxyConfiguration; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; @@ -16,10 +18,13 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.concurrent.ThreadFactory; import java.io.InputStream; +import static io.split.inputValidation.FallbackTreatmentValidator.isValidByFlagTreatment; +import static io.split.inputValidation.FallbackTreatmentValidator.isValidTreatment; import static io.split.inputValidation.FlagSetsValidator.cleanup; /** @@ -91,6 +96,7 @@ private HttpScheme() { private final CustomStorageWrapper _customStorageWrapper; private final StorageMode _storageMode; private final ThreadFactory _threadFactory; + private final FallbackTreatmentsConfiguration _fallbackTreatments; // Proxy configs private final ProxyConfiguration _proxyConfiguration; @@ -163,7 +169,8 @@ private SplitClientConfig(String endpoint, HashSet flagSetsFilter, int invalidSets, CustomHeaderDecorator customHeaderDecorator, - CustomHttpModule alternativeHTTPModule) { + CustomHttpModule alternativeHTTPModule, + FallbackTreatmentsConfiguration fallbackTreatments) { _endpoint = endpoint; _eventsEndpoint = eventsEndpoint; _featuresRefreshRate = pollForFeatureChangesEveryNSeconds; @@ -218,6 +225,7 @@ private SplitClientConfig(String endpoint, _invalidSets = invalidSets; _customHeaderDecorator = customHeaderDecorator; _alternativeHTTPModule = alternativeHTTPModule; + _fallbackTreatments = fallbackTreatments; Properties props = new Properties(); try { @@ -436,6 +444,8 @@ public boolean isSdkEndpointOverridden() { public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; } + public FallbackTreatmentsConfiguration fallbackTreatments() { return _fallbackTreatments; } + public static final class Builder { private String _endpoint = SDK_ENDPOINT; private boolean _endpointSet = false; @@ -494,6 +504,7 @@ public static final class Builder { private int _invalidSetsCount = 0; private CustomHeaderDecorator _customHeaderDecorator = null; private CustomHttpModule _alternativeHTTPModule = null; + private FallbackTreatmentsConfiguration _fallbackTreatments; public Builder() { } @@ -1022,6 +1033,17 @@ public Builder alternativeHTTPModule(CustomHttpModule alternativeHTTPModule) { return this; } + /** + * Fallback Treatments + * + * @param fallbackTreatments + * @return this builder + */ + public Builder fallbackTreatments(FallbackTreatmentsConfiguration fallbackTreatments) { + _fallbackTreatments = fallbackTreatments; + return this; + } + /** * Thread Factory * @@ -1158,6 +1180,25 @@ private void verifyProxy() { } } + private void verifyFallbackTreatments() { + if (_fallbackTreatments == null) + return; + + FallbackTreatment processedGlobalFallbackTreatment = _fallbackTreatments.getGlobalFallbackTreatment(); + Map processedByFlagFallbackTreatment = _fallbackTreatments.getByFlagFallbackTreatment(); + + if (_fallbackTreatments.getGlobalFallbackTreatment() != null) { + processedGlobalFallbackTreatment = new FallbackTreatment( + isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "Fallback treatments"), + _fallbackTreatments.getGlobalFallbackTreatment().getConfig()); + } + + if (_fallbackTreatments.getByFlagFallbackTreatment() != null) { + processedByFlagFallbackTreatment = isValidByFlagTreatment(_fallbackTreatments.getByFlagFallbackTreatment(), "config"); + } + _fallbackTreatments = new FallbackTreatmentsConfiguration(processedGlobalFallbackTreatment, processedByFlagFallbackTreatment); + } + public SplitClientConfig build() { verifyRates(); @@ -1172,6 +1213,8 @@ public SplitClientConfig build() { verifyProxy(); + verifyFallbackTreatments(); + if (_numThreadsForSegmentFetch <= 0) { throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero"); } @@ -1230,7 +1273,8 @@ public SplitClientConfig build() { _flagSetsFilter, _invalidSetsCount, _customHeaderDecorator, - _alternativeHTTPModule); + _alternativeHTTPModule, + _fallbackTreatments); } } } \ No newline at end of file diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 9f4b8ff9e..38ab5e6ba 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -3,9 +3,7 @@ import com.google.gson.GsonBuilder; import io.split.client.api.Key; import io.split.client.api.SplitResult; -import io.split.client.dtos.DecoratedImpression; -import io.split.client.dtos.EvaluationOptions; -import io.split.client.dtos.Event; +import io.split.client.dtos.*; import io.split.client.events.EventsStorageProducer; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; @@ -14,7 +12,6 @@ import io.split.engine.evaluator.Evaluator; import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.evaluator.Labels; -import io.split.grammar.Treatments; import io.split.inputValidation.EventsValidator; import io.split.inputValidation.KeyValidator; import io.split.inputValidation.SplitNameValidator; @@ -49,7 +46,6 @@ * @author adil */ public final class SplitClientImpl implements SplitClient { - public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String CLIENT_DESTROY = "Client has already been destroyed - no calls possible"; private static final String CATCHALL_EXCEPTION = "CatchAll Exception"; private static final String MATCHING_KEY = "matchingKey"; @@ -66,6 +62,7 @@ public final class SplitClientImpl implements SplitClient { private final TelemetryEvaluationProducer _telemetryEvaluationProducer; private final TelemetryConfigProducer _telemetryConfigProducer; private final FlagSetsFilter _flagSetsFilter; + private final FallbackTreatmentCalculator _fallbackTreatmentCalculator; public SplitClientImpl(SplitFactory container, SplitCacheConsumer splitCacheConsumer, @@ -76,7 +73,8 @@ public SplitClientImpl(SplitFactory container, Evaluator evaluator, TelemetryEvaluationProducer telemetryEvaluationProducer, TelemetryConfigProducer telemetryConfigProducer, - FlagSetsFilter flagSetsFilter) { + FlagSetsFilter flagSetsFilter, + FallbackTreatmentCalculator fallbackTreatmentCalculator) { _container = container; _splitCacheConsumer = checkNotNull(splitCacheConsumer); _impressionManager = checkNotNull(impressionManager); @@ -87,6 +85,7 @@ public SplitClientImpl(SplitFactory container, _telemetryEvaluationProducer = checkNotNull(telemetryEvaluationProducer); _telemetryConfigProducer = checkNotNull(telemetryConfigProducer); _flagSetsFilter = flagSetsFilter; + _fallbackTreatmentCalculator = fallbackTreatmentCalculator; } @Override @@ -492,31 +491,34 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu if (_container.isDestroyed()) { _log.error(CLIENT_DESTROY); - return SPLIT_RESULT_CONTROL; + return checkFallbackTreatment(featureFlag); } if (!KeyValidator.isValid(matchingKey, MATCHING_KEY, _config.maxStringLength(), methodEnum.getMethod())) { - return SPLIT_RESULT_CONTROL; + return checkFallbackTreatment(featureFlag); } if (!KeyValidator.bucketingKeyIsValid(bucketingKey, _config.maxStringLength(), methodEnum.getMethod())) { - return SPLIT_RESULT_CONTROL; + return checkFallbackTreatment(featureFlag); } Optional splitNameResult = SplitNameValidator.isValid(featureFlag, methodEnum.getMethod()); if (!splitNameResult.isPresent()) { - return SPLIT_RESULT_CONTROL; + return checkFallbackTreatment(featureFlag); } featureFlag = splitNameResult.get(); long start = System.currentTimeMillis(); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, featureFlag, attributes); - - if (result.treatment.equals(Treatments.CONTROL) && result.label.equals(Labels.DEFINITION_NOT_FOUND) && _gates.isSDKReady()) { - _log.warn(String.format( - "%s: you passed \"%s\" that does not exist in this environment, " + - "please double check what feature flags exist in the Split user interface.", methodEnum.getMethod(), featureFlag)); - return SPLIT_RESULT_CONTROL; + String label = result.label; + if (result.label != null && result.label.contains(Labels.DEFINITION_NOT_FOUND)) { + if (_gates.isSDKReady()) { + _log.warn(String.format( + "%s: you passed \"%s\" that does not exist in this environment, " + + "please double check what feature flags exist in the Split user interface.", methodEnum.getMethod(), featureFlag)); + return checkFallbackTreatment(featureFlag); + } + label = result.label.replace(Labels.DEFINITION_NOT_FOUND, Labels.NOT_READY); } recordStats( @@ -526,7 +528,7 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu start, result.treatment, String.format("sdk.%s", methodEnum.getMethod()), - _config.labelsEnabled() ? result.label : null, + _config.labelsEnabled() ? label : null, result.changeNumber, attributes, result.track, @@ -541,8 +543,17 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu } catch (Exception e1) { // ignore } - return SPLIT_RESULT_CONTROL; + return checkFallbackTreatment(featureFlag); + } + } + + private SplitResult checkFallbackTreatment(String featureName) { + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, ""); + String config = null; + if (fallbackTreatment.getConfig() != null) { + config = fallbackTreatment.getConfig(); } + return new SplitResult(fallbackTreatment.getTreatment(), config); } private String validateProperties(Map properties) { @@ -563,6 +574,7 @@ private Map getTreatmentsWithConfigInternal(String matching _log.error(String.format("%s: featureFlagNames must be a non-empty array", methodEnum.getMethod())); return new HashMap<>(); } + try { checkSDKReady(methodEnum, featureFlagNames); Map result = validateBeforeEvaluate(featureFlagNames, matchingKey, methodEnum, bucketingKey); @@ -601,47 +613,47 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma if (cleanFlagSets.isEmpty()) { return new HashMap<>(); } - List featureFlagNames = new ArrayList<>(); - try { - checkSDKReady(methodEnum); - Map result = validateBeforeEvaluateByFlagSets(matchingKey, methodEnum,bucketingKey); - if(result != null) { - return result; - } - Map evaluatorResult = _evaluator.evaluateFeaturesByFlagSets(matchingKey, - bucketingKey, new ArrayList<>(cleanFlagSets), attributes); + checkSDKReady(methodEnum); + Map result = validateBeforeEvaluateByFlagSets(matchingKey, methodEnum,bucketingKey); + if(result != null) { + return result; + } + Map evaluatorResult = _evaluator.evaluateFeaturesByFlagSets(matchingKey, + bucketingKey, new ArrayList<>(cleanFlagSets), attributes); - return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime, - validateProperties(evaluationOptions.getProperties())); - } catch (Exception e) { - try { + evaluatorResult.entrySet().forEach(flag -> { + if (flag.getValue().label != null && + flag.getValue().label.contains(io.split.engine.evaluator.Labels.EXCEPTION)) { _telemetryEvaluationProducer.recordException(methodEnum); - _log.error(CATCHALL_EXCEPTION, e); - } catch (Exception e1) { - // ignore } - return createMapControl(featureFlagNames); - } + }); + return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime, + validateProperties(evaluationOptions.getProperties())); } + private Map processEvaluatorResult(Map evaluatorResult, MethodEnum methodEnum, String matchingKey, String bucketingKey, Map attributes, long initTime, String properties){ List decoratedImpressions = new ArrayList<>(); Map result = new HashMap<>(); - evaluatorResult.keySet().forEach(t -> { - if (evaluatorResult.get(t).treatment.equals(Treatments.CONTROL) && evaluatorResult.get(t).label. - equals(Labels.DEFINITION_NOT_FOUND) && _gates.isSDKReady()) { - _log.warn(String.format("%s: you passed \"%s\" that does not exist in this environment please double check " + - "what feature flags exist in the Split user interface.", methodEnum.getMethod(), t)); - result.put(t, SPLIT_RESULT_CONTROL); - } else { - result.put(t, new SplitResult(evaluatorResult.get(t).treatment, evaluatorResult.get(t).configurations)); - decoratedImpressions.add( - new DecoratedImpression( - new Impression(matchingKey, bucketingKey, t, evaluatorResult.get(t).treatment, System.currentTimeMillis(), - evaluatorResult.get(t).label, evaluatorResult.get(t).changeNumber, attributes, properties), - evaluatorResult.get(t).track)); + evaluatorResult.keySet().forEach(flag -> { + String label = evaluatorResult.get(flag).label; + if (evaluatorResult.get(flag).label != null && + evaluatorResult.get(flag).label.contains(Labels.DEFINITION_NOT_FOUND)) { + if (_gates.isSDKReady()) { + _log.warn(String.format("%s: you passed \"%s\" that does not exist in this environment please double check " + + "what feature flags exist in the Split user interface.", methodEnum.getMethod(), flag)); + result.put(flag, checkFallbackTreatment(flag)); + return; + } + label = evaluatorResult.get(flag).label.replace(Labels.DEFINITION_NOT_FOUND, Labels.NOT_READY); } + result.put(flag, new SplitResult(evaluatorResult.get(flag).treatment, evaluatorResult.get(flag).configurations)); + decoratedImpressions.add( + new DecoratedImpression( + new Impression(matchingKey, bucketingKey, flag, evaluatorResult.get(flag).treatment, System.currentTimeMillis(), + label, evaluatorResult.get(flag).changeNumber, attributes, properties), + evaluatorResult.get(flag).track)); }); _telemetryEvaluationProducer.recordLatency(methodEnum, System.currentTimeMillis() - initTime); if (!decoratedImpressions.isEmpty()) { @@ -735,7 +747,7 @@ private void checkSDKReady(MethodEnum methodEnum) { private Map createMapControl(List featureFlags) { Map result = new HashMap<>(); - featureFlags.forEach(s -> result.put(s, SPLIT_RESULT_CONTROL)); + featureFlags.forEach(s -> result.put(s, checkFallbackTreatment(s))); return result; } } \ No newline at end of file diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index cca655612..9ad38ef1b 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -2,6 +2,7 @@ import com.google.common.io.Files; import io.split.client.dtos.BearerCredentialsProvider; +import io.split.client.dtos.FallbackTreatmentCalculatorImp; import io.split.client.dtos.Metadata; import io.split.client.events.EventsSender; import io.split.client.events.EventsStorage; @@ -256,8 +257,9 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory()); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(config.fallbackTreatments()); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, fallbackTreatmentCalculatorImp); // SplitClient _client = new SplitClientImpl(this, @@ -269,7 +271,9 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _evaluator, _telemetryStorageProducer, // TelemetryEvaluation instance _telemetryStorageProducer, // TelemetryConfiguration instance - flagSetsFilter); + flagSetsFilter, + fallbackTreatmentCalculatorImp + ); // SplitManager _manager = new SplitManagerImpl(splitCache, config, _gates, _telemetryStorageProducer); @@ -348,7 +352,9 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata); UserCustomRuleBasedSegmentAdapterConsumer userCustomRuleBasedSegmentAdapterConsumer = new UserCustomRuleBasedSegmentAdapterConsumer(customStorageWrapper); - _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(config.fallbackTreatments()); + _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, + userCustomRuleBasedSegmentAdapterConsumer, fallbackTreatmentCalculatorImp); _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); _uniqueKeysTracker = createUniqueKeysTracker(config); _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, @@ -377,7 +383,9 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _evaluator, _telemetryStorageProducer, // TelemetryEvaluation instance _telemetryStorageProducer, // TelemetryConfiguration instance - flagSetsFilter); + flagSetsFilter, + fallbackTreatmentCalculatorImp + ); // SyncManager _syncManager = new ConsumerSyncManager(synchronizer); @@ -445,8 +453,9 @@ protected SplitFactoryImpl(SplitClientConfig config) { SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp, _impressionsManager, null, null, null); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(config.fallbackTreatments()); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, fallbackTreatmentCalculatorImp); EventsStorage eventsStorage = new NoopEventsStorageImp(); @@ -460,7 +469,9 @@ protected SplitFactoryImpl(SplitClientConfig config) { _evaluator, _telemetryStorageProducer, // TelemetryEvaluation instance _telemetryStorageProducer, // TelemetryConfiguration instance - flagSetsFilter); + flagSetsFilter, + fallbackTreatmentCalculatorImp + ); // Synchronizer Synchronizer synchronizer = new LocalhostSynchronizer(splitTasks, _splitFetcher, diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java new file mode 100644 index 000000000..291db4f48 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java @@ -0,0 +1,37 @@ +package io.split.client.dtos; + +public class FallbackTreatment { + private final String _config; + private final String _treatment; + private final String _label; + + public FallbackTreatment(String treatment, String config) { + _treatment = treatment; + _config = config; + _label = null; + } + + public FallbackTreatment(String treatment) { + _treatment = treatment; + _config = null; + _label = null; + } + + public FallbackTreatment(String treatment, String config, String label) { + _treatment = treatment; + _config = config; + _label = label; + } + + public String getConfig() { + return _config; + } + + public String getTreatment() { + return _treatment; + } + + public String getLabel() { + return _label; + } +} diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java new file mode 100644 index 000000000..b172a1cb2 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java @@ -0,0 +1,6 @@ +package io.split.client.dtos; + +public interface FallbackTreatmentCalculator +{ + FallbackTreatment resolve(String flagName, String label); +} diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java new file mode 100644 index 000000000..936abc493 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java @@ -0,0 +1,40 @@ +package io.split.client.dtos; + +import io.split.grammar.Treatments; + +public class FallbackTreatmentCalculatorImp implements FallbackTreatmentCalculator +{ + private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; + private final static String labelPrefix = "fallback - "; + + public FallbackTreatmentCalculatorImp(FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { + _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; + } + + public FallbackTreatment resolve(String flagName, String label) { + if (_fallbackTreatmentsConfiguration != null) { + if (_fallbackTreatmentsConfiguration.getByFlagFallbackTreatment() != null + && _fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(flagName) != null) { + return copyWithLabel(_fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(flagName), + resolveLabel(label)); + } + if (_fallbackTreatmentsConfiguration.getGlobalFallbackTreatment() != null) { + return copyWithLabel(_fallbackTreatmentsConfiguration.getGlobalFallbackTreatment(), + resolveLabel(label)); + } + } + + return new FallbackTreatment(Treatments.CONTROL, null, label); + } + + private String resolveLabel(String label) { + if (label == null) { + return null; + } + return labelPrefix + label; + } + + private FallbackTreatment copyWithLabel(FallbackTreatment fallbackTreatment, String label) { + return new FallbackTreatment(fallbackTreatment.getTreatment(), fallbackTreatment.getConfig(), label); + } +} diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java new file mode 100644 index 000000000..aa47d1163 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java @@ -0,0 +1,19 @@ +package io.split.client.dtos; + +import java.util.Map; + +public class FallbackTreatmentsConfiguration { + private final FallbackTreatment _globalFallbackTreatment; + private final Map _byFlagFallbackTreatment; + + public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment, Map byFlagFallbackTreatment) { + _globalFallbackTreatment = globalFallbackTreatment; + _byFlagFallbackTreatment = byFlagFallbackTreatment; + } + + public FallbackTreatment getGlobalFallbackTreatment() { + return _globalFallbackTreatment; + } + + public Map getByFlagFallbackTreatment() { return _byFlagFallbackTreatment;} +} diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 6d31952c3..8d7147aa6 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,11 +1,12 @@ package io.split.engine.evaluator; import io.split.client.dtos.ConditionType; +import io.split.client.dtos.FallbackTreatment; +import io.split.client.dtos.FallbackTreatmentCalculator; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.splitter.Splitter; -import io.split.grammar.Treatments; import io.split.storages.RuleBasedSegmentCacheConsumer; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; @@ -26,19 +27,23 @@ public class EvaluatorImp implements Evaluator { private final SegmentCacheConsumer _segmentCacheConsumer; private final EvaluationContext _evaluationContext; private final SplitCacheConsumer _splitCacheConsumer; + private final FallbackTreatmentCalculator _fallbackTreatmentCalculator; + private final String _evaluatorException = "Evaluator Exception"; public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache, - RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) { + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer, + FallbackTreatmentCalculator fallbackTreatmentCalculator) { _splitCacheConsumer = checkNotNull(splitCacheConsumer); _segmentCacheConsumer = checkNotNull(segmentCache); _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer); + _fallbackTreatmentCalculator = fallbackTreatmentCalculator; } @Override public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String featureFlag, Map attributes) { ParsedSplit parsedSplit = _splitCacheConsumer.get(featureFlag); - return evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplit); + return evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplit, featureFlag); } @Override @@ -49,7 +54,7 @@ public Map evaluateFeatures(String matchi if (parsedSplits == null) { return results; } - featureFlags.forEach(s -> results.put(s, evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplits.get(s)))); + featureFlags.forEach(s -> results.put(s, evaluateParsedSplit(matchingKey, bucketingKey, attributes, parsedSplits.get(s), s))); return results; } @@ -57,7 +62,27 @@ public Map evaluateFeatures(String matchi public Map evaluateFeaturesByFlagSets(String key, String bucketingKey, List flagSets, Map attributes) { List flagSetsWithNames = getFeatureFlagNamesByFlagSets(flagSets); - return evaluateFeatures(key, bucketingKey, flagSetsWithNames, attributes); + try { + return evaluateFeatures(key, bucketingKey, flagSetsWithNames, attributes); + } catch (Exception e) { + _log.error(_evaluatorException, e); + return createMapControl(flagSetsWithNames, io.split.engine.evaluator.Labels.EXCEPTION); + } + } + + private Map createMapControl(List featureFlags, String label) { + Map result = new HashMap<>(); + featureFlags.forEach(s -> result.put(s, checkFallbackTreatment(s, label))); + return result; + } + + private EvaluatorImp.TreatmentLabelAndChangeNumber checkFallbackTreatment(String featureName, String label) { + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, label); + return new EvaluatorImp.TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), + fallbackTreatment.getLabel(), + null, + getFallbackConfig(fallbackTreatment), + false); } private List getFeatureFlagNamesByFlagSets(List flagSets) { @@ -171,19 +196,34 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { return parsedSplit.configurations() != null ? parsedSplit.configurations().get(returnedTreatment) : null; } + private String getFallbackConfig(FallbackTreatment fallbackTreatment) { + if (fallbackTreatment.getConfig() != null) { + return fallbackTreatment.getConfig(); + } + + return null; + } + private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, - ParsedSplit parsedSplit) { + ParsedSplit parsedSplit, String featureName) { try { if (parsedSplit == null) { - return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.DEFINITION_NOT_FOUND); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), + fallbackTreatment.getLabel(), + null, + getFallbackConfig(fallbackTreatment), + false); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { - _log.error("Evaluator Exception", e.wrappedException()); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION, e.changeNumber()); + _log.error(_evaluatorException, e.wrappedException()); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.EXCEPTION); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel(), e.changeNumber()); } catch (Exception e) { - _log.error("Evaluator Exception", e); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION); + _log.error(_evaluatorException, e); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.EXCEPTION); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel()); } } diff --git a/client/src/main/java/io/split/engine/evaluator/Labels.java b/client/src/main/java/io/split/engine/evaluator/Labels.java index 9bda16a8b..28966d51e 100644 --- a/client/src/main/java/io/split/engine/evaluator/Labels.java +++ b/client/src/main/java/io/split/engine/evaluator/Labels.java @@ -8,4 +8,5 @@ public class Labels { public static final String EXCEPTION = "exception"; public static final String UNSUPPORTED_MATCHER = "targeting rule type unsupported by sdk"; public static final String PREREQUISITES_NOT_MET = "prerequisites not met"; + public static final String NOT_READY = "not ready"; } \ No newline at end of file diff --git a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java new file mode 100644 index 000000000..9fafe5eea --- /dev/null +++ b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java @@ -0,0 +1,67 @@ +package io.split.inputValidation; + +import io.split.client.dtos.FallbackTreatment; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +import static io.split.inputValidation.SplitNameValidator.isValid; + +public class FallbackTreatmentValidator { + private static final Logger _log = LoggerFactory.getLogger(FallbackTreatmentValidator.class); + private static final Pattern TREATMENT_MATCHER = Pattern.compile("^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$"); + private static final int MAX_LENGTH = 100; + + public static String isValidTreatment(String name, String method) { + if (name == null) { + _log.error(String.format("%s: you passed a null treatment, fallback treatment must be a non-empty string", method)); + return null; + } + + if (name.isEmpty()) { + _log.error(String.format("%s: you passed an empty treatment, fallback treatment must be a non-empty string", method)); + return null; + } + + String trimmed = name.trim(); + if (!trimmed.equals(name)) { + _log.warn(String.format("%s: fallback treatment %s has extra whitespace, trimming", method, name)); + name = trimmed; + } + + if (name.length() > MAX_LENGTH) { + return null; + } + + if (!TREATMENT_MATCHER.matcher(name).find()) { + _log.error(String.format("%s: you passed %s, treatment must adhere to the regular expression " + + "^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$", method, name)); + return null; + } + + return name; + } + + public static Map isValidByFlagTreatment(Map byFlagTreatment, String method) { + Map result = new HashMap<>(); + for (Map.Entry entry : byFlagTreatment.entrySet()) { + Optional featureName = isValid(entry.getKey(), method); + if (featureName.equals(Optional.empty()) || !featureName.isPresent()) { + continue; + } + + FallbackTreatment fallbackTreatment = entry.getValue(); + String treatment = isValidTreatment(fallbackTreatment.getTreatment(), method); + if (treatment != null) { + result.put(featureName.get(), new FallbackTreatment(treatment, fallbackTreatment.getConfig())); + } + } + + return result; + } +} diff --git a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java index 72f6a1b9d..f138f51c1 100644 --- a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java +++ b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java @@ -6,10 +6,13 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; public class SplitNameValidator { private static final Logger _log = LoggerFactory.getLogger(SplitNameValidator.class); + private static final int MAX_LENGTH = 100; + private static final Pattern NAME_MATCHER = Pattern.compile("^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$"); public static Optional isValid(String name, String method) { if (name == null) { @@ -28,6 +31,16 @@ public static Optional isValid(String name, String method) { name = trimmed; } + if (name.length() > MAX_LENGTH) { + return Optional.empty(); + } + + if (!NAME_MATCHER.matcher(name).find()) { + _log.error(String.format("%s: you passed %s, feature flag name must adhere to the regular expression " + + "^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$", method, name)); + return Optional.empty(); + } + return Optional.of(name); } diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java index be9e85544..e8ed6dfdb 100644 --- a/client/src/test/java/io/split/client/SplitClientConfigTest.java +++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java @@ -1,13 +1,13 @@ package io.split.client; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.client.dtos.BasicCredentialsProvider; -import io.split.client.dtos.BearerCredentialsProvider; +import io.split.client.dtos.RequestContext; +import io.split.client.dtos.FallbackTreatmentsConfiguration; +import io.split.client.dtos.FallbackTreatment; import io.split.client.dtos.ProxyConfiguration; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; -import io.split.client.dtos.RequestContext; import io.split.integrations.IntegrationsConfig; import org.junit.Assert; import org.junit.Test; @@ -17,6 +17,7 @@ import java.io.FileNotFoundException; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -362,4 +363,25 @@ public void mustUseP12PassKeyWithProxyMtls() throws MalformedURLException, FileN .build()) .build(); } + + @Test + public void fallbackTreatmentCheckRegex() { + SplitClientConfig config = SplitClientConfig.builder() + .fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("12#2"), null)) + .build(); + Assert.assertEquals(null, config.fallbackTreatments().getGlobalFallbackTreatment().getTreatment()); + + config = SplitClientConfig.builder() + .fallbackTreatments(new FallbackTreatmentsConfiguration(null, new HashMap() {{ put("flag", new FallbackTreatment("12#2")); }} )) + .build(); + Assert.assertEquals(0, config.fallbackTreatments().getByFlagFallbackTreatment().size()); + + config = SplitClientConfig.builder() + .fallbackTreatments(new FallbackTreatmentsConfiguration( + new FallbackTreatment("on"), + new HashMap() {{ put("flag", new FallbackTreatment("off")); }} )) + .build(); + Assert.assertEquals("on", config.fallbackTreatments().getGlobalFallbackTreatment().getTreatment()); + Assert.assertEquals("off", config.fallbackTreatments().getByFlagFallbackTreatment().get("flag").getTreatment()); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 5b56708d9..2556508c6 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -99,8 +99,9 @@ public void nullKeyResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatment(null, "test1")); @@ -129,8 +130,9 @@ public void nullTestResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", null)); @@ -152,8 +154,9 @@ public void exceptionsResultInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test1")); @@ -184,8 +187,9 @@ public void works() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -222,8 +226,9 @@ public void worksNullConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); String randomKey = RandomStringUtils.random(10); SplitResult result = client.getTreatmentWithConfig(randomKey, test); @@ -258,8 +263,9 @@ public void worksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -295,8 +301,9 @@ public void lastConditionIsAlwaysDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.OFF, client.getTreatment("pato@codigo.com", test)); @@ -335,8 +342,9 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); SplitResult result = client.getTreatmentWithConfig("pato@codigo.com", test); @@ -371,8 +379,9 @@ public void multipleConditionsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("on", client.getTreatment("adil@codigo.com", test)); @@ -405,8 +414,9 @@ public void killedTestAlwaysGoesToDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.OFF, client.getTreatment("adil@codigo.com", test)); @@ -445,8 +455,9 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); SplitResult result = client.getTreatmentWithConfig("adil@codigo.com", test); @@ -483,8 +494,9 @@ public void dependencyMatcherOn() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.ON, client.getTreatment("key", parent)); @@ -510,7 +522,7 @@ public void dependencyMatcherOff() { RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(parent)).thenReturn(parentSplit); when(splitCacheConsumer.get(dependent)).thenReturn(dependentSplit); - + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), splitCacheConsumer, @@ -518,8 +530,9 @@ public void dependencyMatcherOff() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); assertEquals(Treatments.ON, client.getTreatment("key", parent)); @@ -539,6 +552,7 @@ public void dependencyMatcherControl() { SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(dependent)).thenReturn(dependentSplit); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -547,8 +561,9 @@ public void dependencyMatcherControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); assertEquals(Treatments.ON, client.getTreatment("key", dependent)); @@ -577,8 +592,9 @@ public void attributesWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("on", client.getTreatment("adil@codigo.com", test)); @@ -612,8 +628,9 @@ public void attributesWork2() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); @@ -648,8 +665,9 @@ public void attributesGreaterThanNegativeNumber() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); @@ -686,8 +704,9 @@ public void attributesForSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); @@ -731,8 +750,9 @@ public void labelsArePopulated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -834,8 +854,9 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(expected_treatment_on_or_off, client.getTreatment(key, test)); @@ -888,8 +909,9 @@ public void notInTrafficAllocationDefaultConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.OFF, client.getTreatment("pato@split.io", test)); @@ -932,8 +954,9 @@ public void matchingBucketingKeysWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Key bad_key = new Key("adil", "aijaz"); @@ -975,8 +998,9 @@ public void matchingBucketingKeysByFlagSetWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Key bad_key = new Key("adil", "aijaz"); @@ -1016,8 +1040,9 @@ public void matchingBucketingKeysByFlagSetsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Key bad_key = new Key("adil", "aijaz"); @@ -1054,8 +1079,9 @@ public void impressionMetadataIsPropagated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -1097,8 +1123,9 @@ public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); client.blockUntilReady(); @@ -1119,8 +1146,9 @@ public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, Int NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); client.blockUntilReady(); @@ -1140,8 +1168,9 @@ public void trackWithValidParameters() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertTrue(client.track("validKey", "valid_traffic_type", "valid_event")); @@ -1166,8 +1195,9 @@ public void trackWithInvalidEventTypeIds() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertFalse(client.track("validKey", "valid_traffic_type", "")); Assert.assertFalse(client.track("validKey", "valid_traffic_type", null)); @@ -1191,8 +1221,9 @@ public void trackWithInvalidTrafficTypeNames() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertFalse(client.track("validKey", "", "valid")); @@ -1213,8 +1244,9 @@ public void trackWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertFalse(client.track("", "valid_traffic_type", "valid")); @@ -1245,8 +1277,9 @@ public void getTreatmentWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertNotEquals(Treatments.CONTROL, client.getTreatment("valid", "split")); assertEquals(Treatments.CONTROL, client.getTreatment("", "split")); @@ -1296,8 +1329,9 @@ public void trackWithProperties() { eventClientMock, config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); HashMap properties = new HashMap<>(); @@ -1420,8 +1454,9 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.ON, client.getTreatment("valid", "split")); @@ -1461,8 +1496,9 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1512,8 +1548,9 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1561,8 +1598,9 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1599,8 +1637,9 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); client.blockUntilReady(); @@ -1629,8 +1668,9 @@ public void nullKeyResultsInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatments(null, Collections.singletonList("test1")).get("test1")); @@ -1660,8 +1700,9 @@ public void nullSplitsResultsInEmptyGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(0, client.getTreatments("key", null).size()); @@ -1683,8 +1724,9 @@ public void exceptionsResultInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2")); assertEquals(2, result.values().size()); @@ -1709,6 +1751,7 @@ public void getTreatmentsWorks() { RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(splits); when(gates.isSDKReady()).thenReturn(true); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -1717,8 +1760,9 @@ public void getTreatmentsWorks() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); Map result = client.getTreatments("randomKey", Arrays.asList(test, "test2")); assertEquals("on", result.get(test)); @@ -1748,8 +1792,9 @@ public void emptySplitsResultsInNullGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map result = client.getTreatments("key", new ArrayList<>()); assertNotNull(result); @@ -1773,8 +1818,9 @@ public void exceptionsResultInControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1")); assertEquals(1, result.size()); @@ -1811,8 +1857,9 @@ public void worksTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map result = client.getTreatments("anyKey", Arrays.asList(test, test2)); assertNotNull(result); @@ -1841,6 +1888,7 @@ public void worksOneControlTreatments() { RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); when(gates.isSDKReady()).thenReturn(true); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -1849,8 +1897,9 @@ public void worksOneControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); Map result = client.getTreatments("anyKey", Arrays.asList(test, test2)); @@ -1886,6 +1935,7 @@ public void treatmentsWorksAndHasConfig() { SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -1894,8 +1944,9 @@ public void treatmentsWorksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); Map attributes = new HashMap<>(); Map result = client.getTreatmentsWithConfig("randomKey", Arrays.asList(test, test2, "", null), attributes); @@ -1937,8 +1988,9 @@ public void testTreatmentsByFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1978,8 +2030,9 @@ public void testTreatmentsByFlagSetInvalid() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", new HashMap<>()).isEmpty()); } @@ -2022,8 +2075,9 @@ public void testTreatmentsByFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; Map getTreatmentResult; @@ -2070,6 +2124,7 @@ public void treatmentsWorksAndHasConfigFlagSet() { when(splitCacheConsumer.getNamesByFlagSets(sets)).thenReturn(flagsBySets); SDKReadinessGates gates = mock(SDKReadinessGates.class); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -2078,8 +2133,9 @@ public void treatmentsWorksAndHasConfigFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); Map attributes = new HashMap<>(); Map result = client.getTreatmentsWithConfigByFlagSet("randomKey", "set1", attributes); @@ -2127,6 +2183,7 @@ public void treatmentsWorksAndHasConfigFlagSets() { when(splitCacheConsumer.getNamesByFlagSets(sets)).thenReturn(flagsBySets); SDKReadinessGates gates = mock(SDKReadinessGates.class); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -2135,8 +2192,9 @@ public void treatmentsWorksAndHasConfigFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); Map attributes = new HashMap<>(); Map result = client.getTreatmentsWithConfigByFlagSets("randomKey", new ArrayList<>(Arrays.asList("set1")), attributes); @@ -2181,8 +2239,10 @@ public void impressionPropertiesTest() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - new FlagSetsFilterImpl(new HashSet<>()) + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + new FallbackTreatmentCalculatorImp(null) + ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); EvaluationOptions properties = new EvaluationOptions(new HashMap() @@ -2233,4 +2293,350 @@ public void impressionPropertiesTest() { assertEquals("{\"prop2\":\"val2\",\"prop1\":\"val1\"}", impression.impression().properties()); } } + + @Test + public void fallbackTreatmentWithExceptionsResult() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); + SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); + when(splitCacheConsumer.get(anyString())).thenThrow(RuntimeException.class); + when(splitCacheConsumer.fetchMany(anyList())).thenThrow(RuntimeException.class); + HashMap> features = new HashMap<>(); + features.put("flag", new HashSet<>(Arrays.asList("test1"))); + when(splitCacheConsumer.getNamesByFlagSets(anyList())).thenReturn(features); + + String fallbcakConfigGlobal = "{\"prop1\", \"val1\"}"; + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( + new FallbackTreatment("on", fallbcakConfigGlobal), + null); + FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + + SplitClientImpl client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + new ImpressionsManager.NoOpImpressionsManager(), + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator), + TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + fallbackTreatmentCalculator + ); + assertEquals("on", client.getTreatment("adil@relateiq.com", "test1")); + assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test1").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals("on", client.getTreatments("adil@relateiq.com", Arrays.asList("test1")).get("test1")); + assertEquals("on", client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("test1")).get("test1").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("test1")).get("test1").config()); + + assertEquals("on", client.getTreatmentsByFlagSet("adil@relateiq.com", "flag").get("test1")); + assertEquals("on", client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1")); + assertEquals("on", client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test1").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test1").config()); + assertEquals("on", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1").config()); + + String fallbcakConfigByFlag = "{\"prop2\", \"val2\"}"; + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on", fallbcakConfigGlobal), + new HashMap() {{ put("feature", new FallbackTreatment("off", fallbcakConfigByFlag)); }}); + + features = new HashMap<>(); + features.put("flag", new HashSet<>(Arrays.asList("test", "feature"))); + when(splitCacheConsumer.getNamesByFlagSets(anyList())).thenReturn(features); + + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + + client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + new ImpressionsManager.NoOpImpressionsManager(), + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator), + TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + fallbackTreatmentCalculator + ); + assertEquals("on", client.getTreatment("adil@relateiq.com", "test")); + assertEquals("off", client.getTreatment("adil@relateiq.com", "feature")); + assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test1").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals("off", client.getTreatmentWithConfig("adil@relateiq.com", "feature").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentWithConfig("adil@relateiq.com", "feature").config()); + Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("feature", "test")); + assertEquals("off", result.get("feature")); + assertEquals("on", result.get("test")); + Map results = client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("feature", "test")); + assertEquals("off", results.get("feature").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("feature").config()); + assertEquals("on", results.get("test").treatment()); + assertEquals(fallbcakConfigGlobal, results.get("test").config()); + + assertEquals("on", client.getTreatmentsByFlagSet("adil@relateiq.com", "flag").get("test")); + assertEquals("off", client.getTreatmentsByFlagSet("adil@relateiq.com", "flag").get("feature")); + assertEquals("on", client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test")); + assertEquals("off", client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature")); + assertEquals("on", client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").config()); + assertEquals("off", client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").config()); + assertEquals("on", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test").config()); + assertEquals("off", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").config()); + + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, + new HashMap() {{ put("feature", new FallbackTreatment("off", fallbcakConfigByFlag)); }}); + + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + + client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + new ImpressionsManager.NoOpImpressionsManager(), + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + fallbackTreatmentCalculator + ); + assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test")); + assertEquals("off", client.getTreatment("adil@relateiq.com", "feature")); + assertEquals(Treatments.CONTROL, client.getTreatmentWithConfig("adil@relateiq.com", "test1").treatment()); + assertEquals(null, client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals("off", client.getTreatmentWithConfig("adil@relateiq.com", "feature").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentWithConfig("adil@relateiq.com", "feature").config()); + result = client.getTreatments("adil@relateiq.com", Arrays.asList("feature", "test")); + assertEquals("off", result.get("feature")); + assertEquals(Treatments.CONTROL, result.get("test")); + results = client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("feature", "test")); + assertEquals("off", results.get("feature").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("feature").config()); + assertEquals(Treatments.CONTROL, results.get("test").treatment()); + assertEquals(null, results.get("test").config()); + + assertEquals(Treatments.CONTROL, client.getTreatmentsByFlagSet("adil@relateiq.com", "flag").get("test")); + assertEquals("off", client.getTreatmentsByFlagSet("adil@relateiq.com", "flag").get("feature")); + assertEquals(Treatments.CONTROL, client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test")); + assertEquals("off", client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature")); + assertEquals(Treatments.CONTROL, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").treatment()); + assertEquals(null, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").config()); + assertEquals("off", client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").config()); + assertEquals(Treatments.CONTROL, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test").treatment()); + assertEquals(null, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test").config()); + assertEquals("off", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").config()); + } + + @Test + public void fallbackTreatmentWithSplitNotFoundResult() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); + SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); + ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new WhitelistMatcher(Lists.newArrayList("adil@codigo.com"))), Lists.newArrayList(partition("on", 100))); + List conditions = Lists.newArrayList(rollOutToEveryone); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests("test", 123, false, Treatments.OFF, conditions, + null, 1, 1, new HashSet<>(), false, new PrerequisitesMatcher(null)); + + when(splitCacheConsumer.get("test1")).thenReturn(parsedSplit); + when(splitCacheConsumer.get("test2")).thenReturn(null); + when(splitCacheConsumer.get("test3")).thenReturn(null); + HashMap features = new HashMap<>(); + features.put("test1", parsedSplit); + features.put("test2", null); + features.put("test3", null); + when(splitCacheConsumer.fetchMany(anyList())).thenReturn(features); + HashMap> flagFeatures = new HashMap<>(); + flagFeatures.put("flag", new HashSet<>(Arrays.asList("test1", "test2", "test3"))); + when(splitCacheConsumer.getNamesByFlagSets(anyList())).thenReturn(flagFeatures); + + String fallbcakConfigGlobal = "{\"prop1\", \"val1\"}"; + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( + new FallbackTreatment("on", fallbcakConfigGlobal), + null); + FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + + SplitClientImpl client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + new ImpressionsManager.NoOpImpressionsManager(), + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator), + TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + fallbackTreatmentCalculator + ); + assertEquals("off", client.getTreatment("adil@relateiq.com", "test1")); + assertEquals("on", client.getTreatment("adil@relateiq.com", "test2")); + assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test2").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + + Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2")); + assertEquals("off", result.get("test1")); + assertEquals("on", result.get("test2")); + Map resultWithConfig = client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("test1", "test2")); + assertEquals("off", resultWithConfig.get("test1").treatment()); + assertEquals(null, resultWithConfig.get("test1").config()); + assertEquals("on", resultWithConfig.get("test2").treatment()); + assertEquals(fallbcakConfigGlobal, resultWithConfig.get("test2").config()); + + result = client.getTreatmentsByFlagSet("adil@relateiq.com", "flag"); + assertEquals("off", result.get("test1")); + assertEquals("on", result.get("test2")); + result = client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")); + assertEquals("off", result.get("test1")); + assertEquals("on", result.get("test2")); + resultWithConfig = client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag"); + assertEquals("off", resultWithConfig.get("test1").treatment()); + assertEquals(null, resultWithConfig.get("test1").config()); + assertEquals("on", resultWithConfig.get("test2").treatment()); + assertEquals(fallbcakConfigGlobal, resultWithConfig.get("test2").config()); + resultWithConfig = client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")); + assertEquals("off", resultWithConfig.get("test1").treatment()); + assertEquals(null, resultWithConfig.get("test1").config()); + assertEquals("on", resultWithConfig.get("test2").treatment()); + assertEquals(fallbcakConfigGlobal, resultWithConfig.get("test2").config()); + + String fallbcakConfigByFlag = "{\"prop2\", \"val2\"}"; + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on", fallbcakConfigGlobal), + new HashMap() {{ put("test2", new FallbackTreatment("off-fallback", fallbcakConfigByFlag)); }}); + + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + + client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + new ImpressionsManager.NoOpImpressionsManager(), + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator), + TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + fallbackTreatmentCalculator + ); + assertEquals("off", client.getTreatment("adil@relateiq.com", "test1")); + assertEquals("off-fallback", client.getTreatment("adil@relateiq.com", "test2")); + assertEquals("on", client.getTreatment("adil@relateiq.com", "test3")); + + assertEquals("off", client.getTreatmentWithConfig("adil@relateiq.com", "test1").treatment()); + assertEquals(null, client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals("off-fallback", client.getTreatmentWithConfig("adil@relateiq.com", "test2").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test3").treatment()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentWithConfig("adil@relateiq.com", "test3").config()); + + result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2", "test3")); + assertEquals("off", result.get("test1")); + assertEquals("off-fallback", result.get("test2")); + assertEquals("on", result.get("test3")); + + Map results = client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("test1", "test2", "test3")); + assertEquals("off", results.get("test1").treatment()); + assertEquals(null, results.get("test1").config()); + assertEquals("off-fallback", results.get("test2").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); + assertEquals("on", results.get("test3").treatment()); + assertEquals(fallbcakConfigGlobal, results.get("test3").config()); + + result = client.getTreatmentsByFlagSet("adil@relateiq.com", "flag"); + assertEquals("off", result.get("test1")); + assertEquals("off-fallback", result.get("test2")); + assertEquals("on", result.get("test3")); + + result = client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")); + assertEquals("off", result.get("test1")); + assertEquals("off-fallback", result.get("test2")); + assertEquals("on", result.get("test3")); + + results = client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag"); + assertEquals("off", results.get("test1").treatment()); + assertEquals(null, results.get("test1").config()); + assertEquals("off-fallback", results.get("test2").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); + assertEquals("on", results.get("test3").treatment()); + assertEquals(fallbcakConfigGlobal, results.get("test3").config()); + + results = client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")); + assertEquals("off", results.get("test1").treatment()); + assertEquals(null, results.get("test1").config()); + assertEquals("off-fallback", results.get("test2").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); + assertEquals("on", results.get("test3").treatment()); + assertEquals(fallbcakConfigGlobal, results.get("test3").config()); + + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, + new HashMap() {{ put("test2", new FallbackTreatment("off-fallback", fallbcakConfigByFlag)); }}); + + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + + client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + new ImpressionsManager.NoOpImpressionsManager(), + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()), + fallbackTreatmentCalculator + ); + assertEquals("off", client.getTreatment("adil@relateiq.com", "test1")); + assertEquals("off-fallback", client.getTreatment("adil@relateiq.com", "test2")); + assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test3")); + + assertEquals("off", client.getTreatmentWithConfig("adil@relateiq.com", "test1").treatment()); + assertEquals(null, client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals("off-fallback", client.getTreatmentWithConfig("adil@relateiq.com", "test2").treatment()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + assertEquals(Treatments.CONTROL, client.getTreatmentWithConfig("adil@relateiq.com", "test3").treatment()); + assertEquals(null, client.getTreatmentWithConfig("adil@relateiq.com", "test3").config()); + + result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2", "test3")); + assertEquals("off", result.get("test1")); + assertEquals("off-fallback", result.get("test2")); + assertEquals(Treatments.CONTROL, result.get("test3")); + + results = client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("test1", "test2", "test3")); + assertEquals("off", results.get("test1").treatment()); + assertEquals(null, results.get("test1").config()); + assertEquals("off-fallback", results.get("test2").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); + assertEquals(Treatments.CONTROL, results.get("test3").treatment()); + assertEquals(null, results.get("test3").config()); + + result = client.getTreatmentsByFlagSet("adil@relateiq.com", "flag"); + assertEquals("off", result.get("test1")); + assertEquals("off-fallback", result.get("test2")); + assertEquals(Treatments.CONTROL, result.get("test3")); + + result = client.getTreatmentsByFlagSets("adil@relateiq.com", Arrays.asList("flag")); + assertEquals("off", result.get("test1")); + assertEquals("off-fallback", result.get("test2")); + assertEquals(Treatments.CONTROL, result.get("test3")); + + results = client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag"); + assertEquals("off", results.get("test1").treatment()); + assertEquals(null, results.get("test1").config()); + assertEquals("off-fallback", results.get("test2").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); + assertEquals(Treatments.CONTROL, results.get("test3").treatment()); + assertEquals(null, results.get("test3").config()); + + results = client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")); + assertEquals("off", results.get("test1").treatment()); + assertEquals(null, results.get("test1").config()); + assertEquals("off-fallback", results.get("test2").treatment()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); + assertEquals(Treatments.CONTROL, results.get("test3").treatment()); + assertEquals(null, results.get("test3").config()); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index bba824527..4bdbc598d 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -4,6 +4,8 @@ import io.split.SplitMockServer; import io.split.client.api.SplitView; import io.split.client.dtos.EvaluationOptions; +import io.split.client.dtos.FallbackTreatment; +import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.impressions.ImpressionsManager; import io.split.client.utils.CustomDispatcher; import io.split.storages.enums.OperationMode; @@ -712,10 +714,10 @@ public void testPluggableMode() throws IOException, URISyntaxException { Assert.assertTrue(events.stream().anyMatch(e -> "keyProperties".equals(e.getEventDto().key) && e.getEventDto().properties != null)); Assert.assertEquals(3, splits.size()); - Assert.assertTrue(splits.stream().anyMatch(sw -> "first.name".equals(sw.name))); - Assert.assertTrue(splits.stream().anyMatch(sw -> "second.name".equals(sw.name))); - Assert.assertEquals("on", client.getTreatment("key", "first.name")); - Assert.assertEquals("off", client.getTreatmentWithConfig("FakeKey", "second.name").treatment()); + Assert.assertTrue(splits.stream().anyMatch(sw -> "first-name".equals(sw.name))); + Assert.assertTrue(splits.stream().anyMatch(sw -> "second-name".equals(sw.name))); + Assert.assertEquals("on", client.getTreatment("key", "first-name")); + Assert.assertEquals("off", client.getTreatmentWithConfig("FakeKey", "second-name").treatment()); Assert.assertEquals("control", client.getTreatment("FakeKey", "noSplit")); Assert.assertEquals("on", client.getTreatment("bilal@@split.io", "rbs_flag", new HashMap() {{ put("email", "bilal@@split.io"); @@ -726,8 +728,8 @@ public void testPluggableMode() throws IOException, URISyntaxException { List impressions = customStorageWrapper.getImps(); Assert.assertEquals(4, impressions.size()); - Assert.assertTrue(impressions.stream().anyMatch(imp -> "first.name".equals(imp.getKeyImpression().feature) && "on".equals(imp.getKeyImpression().treatment))); - Assert.assertTrue(impressions.stream().anyMatch(imp -> "second.name".equals(imp.getKeyImpression().feature) && "off".equals(imp.getKeyImpression().treatment))); + Assert.assertTrue(impressions.stream().anyMatch(imp -> "first-name".equals(imp.getKeyImpression().feature) && "on".equals(imp.getKeyImpression().treatment))); + Assert.assertTrue(impressions.stream().anyMatch(imp -> "second-name".equals(imp.getKeyImpression().feature) && "off".equals(imp.getKeyImpression().treatment))); Map latencies = customStorageWrapper.getLatencies(); @@ -1175,6 +1177,299 @@ public MockResponse dispatch(RecordedRequest request) { splitServer.shutdown(); } + @Test + public void FallbackTreatmentGlobalAndByFlagTest() throws Exception { + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); + List allRequests = new ArrayList<>(); + Dispatcher dispatcher = new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + allRequests.add(request); + switch (request.getPath()) { + case "/api/splitChanges?s=1.3&since=-1&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody(splits); + case "/api/splitChanges?s=1.3&since=1602796638344&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody("{\"ff\":{\"d\":[], \"s\":1602796638344, \"t\":1602796638344}, \"rbs\":{\"d\":[],\"s\":-1,\"t\":-1}}"); + case "/api/testImpressions/bulk": + return new MockResponse().setResponseCode(200); + case "/api/testImpressions/count": + return new MockResponse().setResponseCode(200); + case "/v1/keys/ss": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/usage": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/config": + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + }; + + MockWebServer server = new MockWebServer(); + server.setDispatcher(dispatcher); + + server.start(); + String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort()); + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-fallback", "{\"prop1\", \"val1\"}"), + new HashMap() {{ put("feature", new FallbackTreatment("off-fallback", "{\"prop2\", \"val2\"}")); }}); + + SplitClientConfig config = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .endpoint(serverURL, serverURL) + .telemetryURL(serverURL + "/v1") + .authServiceURL(String.format("%s/api/auth/enabled", serverURL)) + .streamingEnabled(false) + .featuresRefreshRate(5) + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .fallbackTreatments(fallbackTreatmentsConfiguration) + .build(); + + SplitFactory factory = SplitFactoryBuilder.build("fake-api-token", config); + SplitClient client = factory.client(); + client.blockUntilReady(); + + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle")); + Assert.assertEquals("off-fallback", client.getTreatmentWithConfig("user2", "feature").treatment()); + Assert.assertEquals("{\"prop2\", \"val2\"}", client.getTreatmentWithConfig("user2", "feature").config()); + Assert.assertEquals("on-fallback", client.getTreatmentWithConfig("user2", "feature2").treatment()); + Assert.assertEquals("{\"prop1\", \"val1\"}", client.getTreatmentWithConfig("user2", "feature2").config()); + + client.destroy(); + boolean check1 = false; + for (int i=0; i < allRequests.size(); i++ ) { + if (allRequests.get(i).getPath().equals("/api/testImpressions/bulk") ) { + String body = allRequests.get(i).getBody().readUtf8(); + if (body.contains("user1")) { + check1 = true; + Assert.assertTrue(body.contains("without_impression_toggle")); + } + } + } + server.shutdown(); + Assert.assertTrue(check1); + } + + @Test + public void FallbackTreatmentGlobalTest() throws Exception { + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); + List allRequests = new ArrayList<>(); + Dispatcher dispatcher = new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + allRequests.add(request); + switch (request.getPath()) { + case "/api/splitChanges?s=1.3&since=-1&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody(splits); + case "/api/splitChanges?s=1.3&since=1602796638344&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody("{\"ff\":{\"d\":[], \"s\":1602796638344, \"t\":1602796638344}, \"rbs\":{\"d\":[],\"s\":-1,\"t\":-1}}"); + case "/api/testImpressions/bulk": + return new MockResponse().setResponseCode(200); + case "/api/testImpressions/count": + return new MockResponse().setResponseCode(200); + case "/v1/keys/ss": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/usage": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/config": + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + }; + + MockWebServer server = new MockWebServer(); + server.setDispatcher(dispatcher); + + server.start(); + String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort()); + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-fallback", "{\"prop1\", \"val1\"}"), + null); + + SplitClientConfig config = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .endpoint(serverURL, serverURL) + .telemetryURL(serverURL + "/v1") + .authServiceURL(String.format("%s/api/auth/enabled", serverURL)) + .streamingEnabled(false) + .featuresRefreshRate(5) + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .fallbackTreatments(fallbackTreatmentsConfiguration) + .build(); + + SplitFactory factory = SplitFactoryBuilder.build("fake-api-token", config); + SplitClient client = factory.client(); + client.blockUntilReady(); + + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle")); + Assert.assertEquals("on-fallback", client.getTreatmentWithConfig("user2", "feature").treatment()); + Assert.assertEquals("{\"prop1\", \"val1\"}", client.getTreatmentWithConfig("user2", "feature").config()); + Assert.assertEquals("on-fallback", client.getTreatmentWithConfig("user2", "feature2").treatment()); + Assert.assertEquals("{\"prop1\", \"val1\"}", client.getTreatmentWithConfig("user2", "feature2").config()); + + client.destroy(); + boolean check1 = false; + for (int i=0; i < allRequests.size(); i++ ) { + if (allRequests.get(i).getPath().equals("/api/testImpressions/bulk") ) { + String body = allRequests.get(i).getBody().readUtf8(); + if (body.contains("user1")) { + check1 = true; + Assert.assertTrue(body.contains("without_impression_toggle")); + } + } + } + server.shutdown(); + Assert.assertTrue(check1); + } + + @Test + public void FallbackTreatmentByFlagTest() throws Exception { + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); + List allRequests = new ArrayList<>(); + Dispatcher dispatcher = new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + allRequests.add(request); + switch (request.getPath()) { + case "/api/splitChanges?s=1.3&since=-1&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody(splits); + case "/api/splitChanges?s=1.3&since=1602796638344&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody("{\"ff\":{\"d\":[], \"s\":1602796638344, \"t\":1602796638344}, \"rbs\":{\"d\":[],\"s\":-1,\"t\":-1}}"); + case "/api/testImpressions/bulk": + return new MockResponse().setResponseCode(200); + case "/api/testImpressions/count": + return new MockResponse().setResponseCode(200); + case "/v1/keys/ss": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/usage": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/config": + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + }; + + MockWebServer server = new MockWebServer(); + server.setDispatcher(dispatcher); + + server.start(); + String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort()); + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, + new HashMap() {{ put("feature", new FallbackTreatment("off-fallback", "{\"prop2\", \"val2\"}")); }}); + + SplitClientConfig config = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .endpoint(serverURL, serverURL) + .telemetryURL(serverURL + "/v1") + .authServiceURL(String.format("%s/api/auth/enabled", serverURL)) + .streamingEnabled(false) + .featuresRefreshRate(5) + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .fallbackTreatments(fallbackTreatmentsConfiguration) + .build(); + + SplitFactory factory = SplitFactoryBuilder.build("fake-api-token", config); + SplitClient client = factory.client(); + client.blockUntilReady(); + + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle")); + Assert.assertEquals("off-fallback", client.getTreatmentWithConfig("user2", "feature").treatment()); + Assert.assertEquals("{\"prop2\", \"val2\"}", client.getTreatmentWithConfig("user2", "feature").config()); + Assert.assertEquals("control", client.getTreatmentWithConfig("user2", "feature2").treatment()); + Assert.assertEquals(null, client.getTreatmentWithConfig("user2", "feature2").config()); + + client.destroy(); + boolean check1 = false; + for (int i=0; i < allRequests.size(); i++ ) { + if (allRequests.get(i).getPath().equals("/api/testImpressions/bulk") ) { + String body = allRequests.get(i).getBody().readUtf8(); + if (body.contains("user1")) { + check1 = true; + Assert.assertTrue(body.contains("without_impression_toggle")); + } + } + } + server.shutdown(); + Assert.assertTrue(check1); + } + + @Test + public void FallbackTreatmentNotReadyTest() throws Exception { + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); + List allRequests = new ArrayList<>(); + Dispatcher dispatcher = new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + allRequests.add(request); + switch (request.getPath()) { + case "/api/splitChanges?s=1.3&since=-1&rbSince=-1": + Thread.sleep(1000); + return new MockResponse().setResponseCode(200).setBody(splits); + case "/api/splitChanges?s=1.3&since=1602796638344&rbSince=-1": + return new MockResponse().setResponseCode(200).setBody("{\"ff\":{\"d\":[], \"s\":1602796638344, \"t\":1602796638344}, \"rbs\":{\"d\":[],\"s\":-1,\"t\":-1}}"); + case "/api/testImpressions/bulk": + return new MockResponse().setResponseCode(200); + case "/api/testImpressions/count": + return new MockResponse().setResponseCode(200); + case "/v1/keys/ss": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/usage": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/config": + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + }; + + MockWebServer server = new MockWebServer(); + server.setDispatcher(dispatcher); + + server.start(); + String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort()); + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-fallback", "{\"prop1\", \"val1\"}"), + null); + + SplitClientConfig config = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .endpoint(serverURL, serverURL) + .telemetryURL(serverURL + "/v1") + .authServiceURL(String.format("%s/api/auth/enabled", serverURL)) + .streamingEnabled(false) + .featuresRefreshRate(5) + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .fallbackTreatments(fallbackTreatmentsConfiguration) + .build(); + + SplitFactory factory = SplitFactoryBuilder.build("fake-api-token", config); + SplitClient client = factory.client(); + + Assert.assertEquals("on-fallback", client.getTreatment("user1", "without_impression_toggle")); + Assert.assertEquals("on-fallback", client.getTreatment("user2", "feature")); + client.blockUntilReady(); + + client.destroy(); + boolean check1 = false, check2 = false; + for (int i=0; i < allRequests.size(); i++ ) { + if (allRequests.get(i).getPath().equals("/api/testImpressions/bulk") ) { + String body = allRequests.get(i).getBody().readUtf8(); + if (body.contains("user2")) { + check1 = true; + Assert.assertTrue(body.contains("feature")); + Assert.assertTrue(body.contains("fallback - not ready")); + } + if (body.contains("user1")) { + check2 = true; + Assert.assertTrue(body.contains("without_impression_toggle")); + Assert.assertTrue(body.contains("fallback - not ready")); + } + } + } + server.shutdown(); + Assert.assertTrue(check1); + Assert.assertTrue(check2); + } + private SSEMockServer buildSSEMockServer(SSEMockServer.SseEventQueue eventQueue) { return new SSEMockServer(eventQueue, (token, version, channel) -> { if (!"1.1".equals(version)) { diff --git a/client/src/test/java/io/split/client/SplitFactoryImplTest.java b/client/src/test/java/io/split/client/SplitFactoryImplTest.java index 9826b47e2..dcf51055c 100644 --- a/client/src/test/java/io/split/client/SplitFactoryImplTest.java +++ b/client/src/test/java/io/split/client/SplitFactoryImplTest.java @@ -1,8 +1,12 @@ package io.split.client; +import io.split.client.dtos.FallbackTreatment; +import io.split.client.dtos.FallbackTreatmentCalculatorImp; +import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.dtos.ProxyConfiguration; import io.split.client.impressions.ImpressionsManager; import io.split.client.utils.FileTypeEnum; +import io.split.engine.evaluator.EvaluatorImp; import io.split.integrations.IntegrationsConfig; import io.split.service.SplitHttpClientImpl; import io.split.storages.enums.OperationMode; @@ -56,11 +60,26 @@ public void testFactoryInstantiation() throws Exception { .authServiceURL(AUTH_SERVICE) .setBlockUntilReadyTimeout(10000) .telemetryURL(SplitClientConfig.TELEMETRY_ENDPOINT) + .fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null)) .build(); SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig); assertNotNull(splitFactory.client()); assertNotNull(splitFactory.manager()); + + Field fallbackField = SplitClientImpl.class.getDeclaredField("_fallbackTreatmentCalculator"); + fallbackField.setAccessible(true); + FallbackTreatmentCalculatorImp fallbackCalc = (FallbackTreatmentCalculatorImp) fallbackField.get(splitFactory.client()); + assertNotNull(fallbackCalc); + + Field evalField = SplitClientImpl.class.getDeclaredField("_evaluator"); + evalField.setAccessible(true); + EvaluatorImp evaluatorImp = (EvaluatorImp) evalField.get(splitFactory.client()); + assertNotNull(fallbackCalc); + fallbackField = EvaluatorImp.class.getDeclaredField("_fallbackTreatmentCalculator"); + fallbackField.setAccessible(true); + fallbackCalc = (FallbackTreatmentCalculatorImp) fallbackField.get(evaluatorImp); + assertNotNull(fallbackCalc); } @Test @@ -365,6 +384,20 @@ public void testFactoryConsumerInstantiation() throws Exception { Thread.sleep(1500); Mockito.verify(userStorageWrapper, Mockito.times(1)).connect(); Mockito.verify(telemetrySynchronizer, Mockito.times(1)).synchronizeConfig(Mockito.anyObject(), Mockito.anyLong(), Mockito.anyObject(), Mockito.anyObject()); + + Field fallbackField = SplitClientImpl.class.getDeclaredField("_fallbackTreatmentCalculator"); + fallbackField.setAccessible(true); + FallbackTreatmentCalculatorImp fallbackCalc = (FallbackTreatmentCalculatorImp) fallbackField.get(splitFactory.client()); + assertNotNull(fallbackCalc); + + Field evalField = SplitClientImpl.class.getDeclaredField("_evaluator"); + evalField.setAccessible(true); + EvaluatorImp evaluatorImp = (EvaluatorImp) evalField.get(splitFactory.client()); + assertNotNull(fallbackCalc); + fallbackField = EvaluatorImp.class.getDeclaredField("_fallbackTreatmentCalculator"); + fallbackField.setAccessible(true); + fallbackCalc = (FallbackTreatmentCalculatorImp) fallbackField.get(evaluatorImp); + assertNotNull(fallbackCalc); } @Test diff --git a/client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java b/client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java new file mode 100644 index 000000000..4e082e007 --- /dev/null +++ b/client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java @@ -0,0 +1,40 @@ +package io.split.client.dtos; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class FallbackTreatmentCalculationImpTest { + + @Test + public void TestWorks() { + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null); + FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + assertEquals("on", fallbackTreatmentCalculator.resolve("anyflag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("anyflag", "exception").getLabel()); + + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), + new HashMap() {{ put("flag", new FallbackTreatment("off")); }} ); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + assertEquals("on", fallbackTreatmentCalculator.resolve("anyflag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("anyflag", "exception").getLabel()); + assertEquals("off", fallbackTreatmentCalculator.resolve("flag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("flag", "exception").getLabel()); + + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, + new HashMap() {{ put("flag", new FallbackTreatment("off")); }} ); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + assertEquals("control", fallbackTreatmentCalculator.resolve("anyflag", "exception").getTreatment()); + assertEquals("exception", fallbackTreatmentCalculator.resolve("anyflag", "exception").getLabel()); + assertEquals("off", fallbackTreatmentCalculator.resolve("flag", "exception").getTreatment()); + assertEquals("fallback - exception", fallbackTreatmentCalculator.resolve("flag", "exception").getLabel()); + } +} diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java index 5cc6d01d9..5b0a024a6 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import io.split.client.dtos.ConditionType; +import io.split.client.dtos.FallbackTreatmentCalculatorImp; import io.split.client.dtos.MatcherCombiner; import io.split.client.dtos.Partition; import io.split.client.interceptors.FlagSetsFilter; @@ -174,7 +175,8 @@ private Evaluator buildEvaluatorAndLoadCache(boolean killed, int trafficAllocati SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp(); - Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); + Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, fallbackTreatmentCalculatorImp); Partition partition = new Partition(); partition.treatment = ON_TREATMENT; diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index cf166bd2b..05a87a611 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -1,8 +1,6 @@ package io.split.engine.evaluator; -import io.split.client.dtos.ConditionType; -import io.split.client.dtos.Partition; -import io.split.client.dtos.Prerequisites; +import io.split.client.dtos.*; import io.split.client.utils.Json; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -50,7 +48,7 @@ public void before() { _splitCacheConsumer = Mockito.mock(SplitCacheConsumer.class); _segmentCacheConsumer = Mockito.mock(SegmentCacheConsumer.class); _ruleBasedSegmentCacheConsumer = Mockito.mock(RuleBasedSegmentCacheConsumer.class); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, new FallbackTreatmentCalculatorImp(null)); _matcher = Mockito.mock(CombiningMatcher.class); _evaluationContext = Mockito.mock(EvaluationContext.class); @@ -226,4 +224,76 @@ public void evaluateWithPrerequisites() { assertEquals(Labels.KILLED, result.label); assertEquals(CHANGE_NUMBER, result.changeNumber); } + + @Test + public void evaluateFallbackTreatmentWorks() { + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null); + FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("on", result.treatment); + assertEquals("fallback - definition not found", result.label); + + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("on", result.treatment); + assertEquals("fallback - exception", result.label); + + // using byflag only + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(null); + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, new HashMap() {{ put(SPLIT_NAME, new FallbackTreatment("off")); }} ); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - definition not found", result.label); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("control", result.treatment); + assertEquals("definition not found", result.label); + + split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - exception", result.label); + + split = new ParsedSplit("another_name", 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("control", result.treatment); + assertEquals("exception", result.label); + + // with byflag + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(null); + fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), new HashMap() {{ put(SPLIT_NAME, new FallbackTreatment("off")); }} ); + fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculator); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - definition not found", result.label); + + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("on", result.treatment); + assertEquals("fallback - definition not found", result.label); + + split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); + assertEquals("off", result.treatment); + assertEquals("fallback - exception", result.label); + + split = new ParsedSplit("another_name", 0, false, DEFAULT_TREATMENT_VALUE, _conditions, null, CHANGE_NUMBER, 60, 18, 2, _configurations, new HashSet<>(), false, null); + Mockito.when(_splitCacheConsumer.get("another_name")).thenReturn(split); + result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, "another_name", null); + assertEquals("on", result.treatment); + assertEquals("fallback - exception", result.label); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/experiments/RuleBasedSegmentParserTest.java b/client/src/test/java/io/split/engine/experiments/RuleBasedSegmentParserTest.java index 526e44491..add3eb2a5 100644 --- a/client/src/test/java/io/split/engine/experiments/RuleBasedSegmentParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/RuleBasedSegmentParserTest.java @@ -63,7 +63,7 @@ public void works() { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher employeesMatcherLogic = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher(EMPLOYEES)); @@ -72,7 +72,7 @@ public void works() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -103,14 +103,14 @@ public void worksForTwoConditions() { List conditions = Lists.newArrayList(c1, c2); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); ParsedCondition parsedCondition1 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(EMPLOYEES)), fullyRollout); ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(EMPLOYEES)), turnOff); List listOfParsedConditions = Lists.newArrayList(parsedCondition1, parsedCondition2); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfParsedConditions, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfParsedConditions, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -135,7 +135,7 @@ public void successForLongConditions() { } RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); Assert.assertNotNull(parser.parse(ruleBasedSegment)); } @@ -163,7 +163,7 @@ public void worksWithAttributes() { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher employeesMatcherLogic = new AttributeMatcher("name", new UserDefinedSegmentMatcher(EMPLOYEES), false); @@ -172,7 +172,7 @@ public void worksWithAttributes() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -191,7 +191,7 @@ public void lessThanOrEqualTo() { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher ageLessThan10Logic = new AttributeMatcher("age", new LessThanOrEqualToMatcher(10, DataType.NUMBER), false); @@ -199,7 +199,7 @@ public void lessThanOrEqualTo() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -217,7 +217,7 @@ public void equalTo() { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher equalToMatcher = new AttributeMatcher("age", new EqualToMatcher(10, DataType.NUMBER), true); @@ -225,7 +225,7 @@ public void equalTo() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -243,7 +243,7 @@ public void equalToNegativeNumber() { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher ageEqualTo10Logic = new AttributeMatcher("age", new EqualToMatcher(-10, DataType.NUMBER), false); @@ -251,7 +251,7 @@ public void equalToNegativeNumber() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -275,7 +275,7 @@ public void between() { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher ageBetween10And11Logic = new AttributeMatcher("age", new BetweenMatcher(10, 12, DataType.NUMBER), false); @@ -283,7 +283,7 @@ public void between() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); @@ -530,7 +530,7 @@ public void setMatcherTest(Condition c, io.split.engine.matchers.Matcher m) { List conditions = Lists.newArrayList(c); RuleBasedSegmentParser parser = new RuleBasedSegmentParser(); - RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first.name", conditions, 1); + RuleBasedSegment ruleBasedSegment = makeRuleBasedSegment("first-name", conditions, 1); ParsedRuleBasedSegment actual = parser.parse(ruleBasedSegment); AttributeMatcher attrMatcher = new AttributeMatcher("products", m, false); @@ -538,7 +538,7 @@ public void setMatcherTest(Condition c, io.split.engine.matchers.Matcher m) { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, null); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first.name", listOfMatcherAndSplits, "user", 1, + ParsedRuleBasedSegment expected = ParsedRuleBasedSegment.createParsedRuleBasedSegmentForTests ("first-name", listOfMatcherAndSplits, "user", 1, new ArrayList<>(), new ArrayList<>()); Assert.assertEquals(actual, expected); diff --git a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java index 4676a8c3b..d9e945bfa 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java @@ -85,7 +85,7 @@ public void works() { List conditions = Lists.newArrayList(c); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -95,7 +95,7 @@ public void works() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); assertTrue(expected.hashCode() != 0); @@ -128,7 +128,7 @@ public void worksWithConfig() { Map configurations = new HashMap<>(); configurations.put("on", "{\"size\":15,\"test\":20}"); configurations.put("off", "{\"size\":10}"); - Split split = makeSplit("first.name", 123, conditions, 1, configurations); + Split split = makeSplit("first-name", 123, conditions, 1, configurations); ParsedSplit actual = parser.parse(split); @@ -138,7 +138,7 @@ public void worksWithConfig() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, configurations, new HashSet<>(), false, new PrerequisitesMatcher(null)); Assert.assertEquals(actual.parsedConditions(), expected.parsedConditions()); @@ -184,7 +184,7 @@ public void worksForTwoConditions() { List conditions = Lists.newArrayList(c1, c2); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -192,7 +192,7 @@ public void worksForTwoConditions() { ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(SALES_PEOPLE)), turnOff); List listOfParsedConditions = Lists.newArrayList(parsedCondition1, parsedCondition2); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfParsedConditions, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfParsedConditions, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); } @@ -218,7 +218,7 @@ public void successForLongConditions() { conditions.add(c); } - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); Assert.assertNotNull(parser.parse(split)); } @@ -251,7 +251,7 @@ public void worksWithAttributes() { List conditions = Lists.newArrayList(c); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -261,7 +261,7 @@ public void worksWithAttributes() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); } @@ -285,7 +285,7 @@ public void lessThanOrEqualTo() { List conditions = Lists.newArrayList(c); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -294,7 +294,7 @@ public void lessThanOrEqualTo() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); } @@ -317,7 +317,7 @@ public void equalTo() { List conditions = Lists.newArrayList(c); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -326,7 +326,7 @@ public void equalTo() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); } @@ -348,7 +348,7 @@ public void equalToNegativeNumber() { List conditions = Lists.newArrayList(c); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -357,7 +357,7 @@ public void equalToNegativeNumber() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); } @@ -384,7 +384,7 @@ public void between() { List conditions = Lists.newArrayList(c); - Split split = makeSplit("first.name", 123, conditions, 1); + Split split = makeSplit("first-name", 123, conditions, 1); ParsedSplit actual = parser.parse(split); @@ -393,7 +393,7 @@ public void between() { ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first-name", 123, false, Treatments.OFF, listOfMatcherAndSplits, "user", 1, 1, null, false, new PrerequisitesMatcher(null)); compareParsed(actual, expected); } diff --git a/client/src/test/java/io/split/inputValidation/FallbackTreatmentValidatorTest.java b/client/src/test/java/io/split/inputValidation/FallbackTreatmentValidatorTest.java new file mode 100644 index 000000000..6bfc4bd9d --- /dev/null +++ b/client/src/test/java/io/split/inputValidation/FallbackTreatmentValidatorTest.java @@ -0,0 +1,50 @@ +package io.split.inputValidation; + +import io.split.client.dtos.FallbackTreatment; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class FallbackTreatmentValidatorTest { + + @Test + public void isValidTreatmentWorks() { + Assert.assertEquals("123asHs_-sdf", FallbackTreatmentValidator.isValidTreatment("123asHs_-sdf", "test")); + + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment(new String(new char[101]).replace('\0', 'w'), "test")); + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment("", "test")); + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment(null, "test")); + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment("12@3asHs_-sdf", "test")); + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment("12#3asHs_-sdf", "test")); + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment("12!3asHs_-sdf", "test")); + Assert.assertEquals(null, FallbackTreatmentValidator.isValidTreatment("12^3asHs_-sdf", "test")); + } + + @Test + public void isValidByFlagTreatmentWorks() { + HashMap byRef = new HashMap() {{ put("flag", new FallbackTreatment("12#2")); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put("flag", new FallbackTreatment("12%2")); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put("flag", new FallbackTreatment(new String(new char[101]).replace('\0', 'w'))); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put("flag", new FallbackTreatment("12&2")); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put("", new FallbackTreatment("on")); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put("12#dd", new FallbackTreatment("on")); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put(new String(new char[101]).replace('\0', 'w'), new FallbackTreatment("on")); }}; + Assert.assertEquals(new HashMap<>(), FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test")); + + byRef = new HashMap() {{ put("flag", new FallbackTreatment("123asHs_-sdf")); }}; + Assert.assertEquals("123asHs_-sdf", FallbackTreatmentValidator.isValidByFlagTreatment(byRef, "test").get("flag").getTreatment()); + } +} diff --git a/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java b/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java index d8db6567c..cb325dfc6 100644 --- a/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java +++ b/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java @@ -22,9 +22,29 @@ public void isValidWorks() { result = SplitNameValidator.isValid("", "test"); Assert.assertFalse(result.isPresent()); + // test regex + result = SplitNameValidator.isValid("te#fg", "test"); + Assert.assertFalse(result.isPresent()); + + // test regex + result = SplitNameValidator.isValid("te@fg", "test"); + Assert.assertFalse(result.isPresent()); + + // test regex + result = SplitNameValidator.isValid("te&fg", "test"); + Assert.assertFalse(result.isPresent()); + + // test regex + result = SplitNameValidator.isValid("te)fg", "test"); + Assert.assertFalse(result.isPresent()); + + // test length + result = SplitNameValidator.isValid(new String(new char[101]).replace('\0', 'w'), "test"); + Assert.assertFalse(result.isPresent()); + // when split name have empty spaces - result = SplitNameValidator.isValid(" split name test ", "test"); + result = SplitNameValidator.isValid(" split-name-test ", "test"); Assert.assertTrue(result.isPresent()); - Assert.assertEquals("split name test", result.get()); + Assert.assertEquals("split-name-test", result.get()); } } diff --git a/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java b/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java index 728ffec78..8733f3d12 100644 --- a/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java +++ b/client/src/test/java/io/split/storages/pluggable/CustomStorageWrapperImp.java @@ -271,8 +271,8 @@ else if(key.startsWith(FLAG_SET)) private void updateCache(){ Condition condition = ConditionsTestUtil.makeUserDefinedSegmentCondition(ConditionType.WHITELIST,"segmentName" , Lists.newArrayList(ConditionsTestUtil.partition("on", 100))); segmentStorage.put(PrefixAdapter.buildSegment("segmentName"), new SegmentImp(9874654L, "segmentName", Lists.newArrayList("key", "key2"))); - splitsStorage.put(PrefixAdapter.buildSplitKey("first.name"), makeSplit("first.name", 123, Lists.newArrayList(condition), 456478976L)); - splitsStorage.put(PrefixAdapter.buildSplitKey("second.name"), makeSplit("second.name", 321, Lists.newArrayList(), 568613L)); + splitsStorage.put(PrefixAdapter.buildSplitKey("first-name"), makeSplit("first-name", 123, Lists.newArrayList(condition), 456478976L)); + splitsStorage.put(PrefixAdapter.buildSplitKey("second-name"), makeSplit("second-name", 321, Lists.newArrayList(), 568613L)); splitsStorage.put(PrefixAdapter.buildSplitKey("rbs_flag"), Json.fromJson("{\"changeNumber\": 10, \"trafficTypeName\": \"user\", \"name\": \"rbs_flag\", \"trafficAllocation\": 100, \"trafficAllocationSeed\": 1828377380, \"seed\": -286617921, \"status\": \"ACTIVE\", \"killed\": false, \"defaultTreatment\": \"off\", \"algo\": 2, \"conditions\": [{\"conditionType\": \"ROLLOUT\", \"matcherGroup\": {\"combiner\": \"AND\", \"matchers\": [{\"keySelector\": {\"trafficType\": \"user\"},\"matcherType\": \"IN_RULE_BASED_SEGMENT\", \"negate\": false, \"userDefinedSegmentMatcherData\": {\"segmentName\": \"sample_rule_based_segment\"}}]},\"partitions\": [{\"treatment\": \"on\", \"size\": 100},{\"treatment\": \"off\", \"size\": 0}],\"label\": \"in rule based segment sample_rule_based_segment\"},{\"conditionType\": \"ROLLOUT\", \"matcherGroup\": {\"combiner\": \"AND\", \"matchers\": [{\"keySelector\": {\"trafficType\": \"user\"},\"matcherType\": \"ALL_KEYS\", \"negate\": false}]},\"partitions\": [{\"treatment\": \"on\", \"size\": 0},{\"treatment\": \"off\", \"size\": 100}],\"label\": \"default rule\"}],\"configurations\": {},\"sets\": [],\"impressionsDisabled\": false}", Split.class)); ruleBasedSegmentStorage.put(PrefixAdapter.buildRuleBasedSegmentKey("sample_rule_based_segment"), Json.fromJson( "{\"changeNumber\":5,\"name\":\"sample_rule_based_segment\",\"status\":\"ACTIVE\",\"trafficTypeName\":\"user\",\"excluded\":{\"keys\":[\"mauro@split.io\"],\"segments\":[]},\"conditions\":[{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"user\",\"attribute\":\"email\"},\"matcherType\":\"ENDS_WITH\",\"negate\":false,\"whitelistMatcherData\":{\"whitelist\":[\"@split.io\"]}}]}}]}", RuleBasedSegment.class)); _flagSets.put("SPLITIO.flagSet.set1", new HashSet<>(new ArrayList<>(Arrays.asList("flag1", "flag2")))); diff --git a/okhttp-modules/pom.xml b/okhttp-modules/pom.xml index fc646f3d7..ec03d10dd 100644 --- a/okhttp-modules/pom.xml +++ b/okhttp-modules/pom.xml @@ -5,10 +5,10 @@ java-client-parent io.split.client - 4.17.0 + 4.18.0 4.0.0 - 4.17.0 + 4.18.0 okhttp-modules jar http-modules diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml index 4b7e01562..94604cfdc 100644 --- a/pluggable-storage/pom.xml +++ b/pluggable-storage/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.17.0 + 4.18.0 2.1.0 diff --git a/pom.xml b/pom.xml index c20814b19..90b70d29b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.split.client java-client-parent - 4.17.0 + 4.18.0 diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml index 8bf6c4246..c39bc2cd4 100644 --- a/redis-wrapper/pom.xml +++ b/redis-wrapper/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.17.0 + 4.18.0 redis-wrapper 3.1.1 diff --git a/testing/pom.xml b/testing/pom.xml index b7f0bf906..cd4ae5504 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -5,11 +5,11 @@ io.split.client java-client-parent - 4.17.0 + 4.18.0 java-client-testing jar - 4.17.0 + 4.18.0 Java Client For Testing Testing suite for Java SDK for Split