From 57cac150cf1fc64a8e2e3d8db9f54b06c6556450 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 3 Sep 2025 20:57:08 -0700 Subject: [PATCH 01/21] Added models and updated config and validator --- .../io/split/client/SplitClientConfig.java | 43 +++++++++++- .../split/client/dtos/FallbackTreatment.java | 33 +++++++++ .../dtos/FallbackTreatmentsConfiguration.java | 25 +++++++ .../FallbackTreatmentValidator.java | 69 +++++++++++++++++++ .../inputValidation/SplitNameValidator.java | 13 ++++ .../split/client/SplitClientConfigTest.java | 28 +++++++- .../client/SplitClientIntegrationTest.java | 12 ++-- .../RuleBasedSegmentParserTest.java | 34 ++++----- .../engine/experiments/SplitParserTest.java | 34 ++++----- .../FallbackTreatmentValidatorTest.java | 50 ++++++++++++++ .../SplitNameValidatorTest.java | 24 ++++++- .../pluggable/CustomStorageWrapperImp.java | 4 +- 12 files changed, 320 insertions(+), 49 deletions(-) create mode 100644 client/src/main/java/io/split/client/dtos/FallbackTreatment.java create mode 100644 client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java create mode 100644 client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java create mode 100644 client/src/test/java/io/split/inputValidation/FallbackTreatmentValidatorTest.java diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index f32b9b091..8dea4581a 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; @@ -20,6 +22,8 @@ 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 +95,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 +168,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 +224,7 @@ private SplitClientConfig(String endpoint, _invalidSets = invalidSets; _customHeaderDecorator = customHeaderDecorator; _alternativeHTTPModule = alternativeHTTPModule; + _fallbackTreatments = fallbackTreatments; Properties props = new Properties(); try { @@ -436,6 +443,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 +503,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 +1032,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 +1179,21 @@ private void verifyProxy() { } } + private void verifyFallbackTreatments() { + if (_fallbackTreatments == null) + return; + + if (_fallbackTreatments.getGlobalFallbackTreatment() != null) { + _fallbackTreatments.setGlobalFallbackTreatment(new FallbackTreatment( + isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "config"), + _fallbackTreatments.getGlobalFallbackTreatment().getConfig())); + } + + if (_fallbackTreatments.getByFlagFallbackTreatment() != null) { + _fallbackTreatments.setByFlagFallbackTreatment(isValidByFlagTreatment(_fallbackTreatments.getByFlagFallbackTreatment(), "config")); + } + } + public SplitClientConfig build() { verifyRates(); @@ -1172,6 +1208,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 +1268,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/dtos/FallbackTreatment.java b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java new file mode 100644 index 000000000..c4f406e4a --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java @@ -0,0 +1,33 @@ +package io.split.client.dtos; + +import java.util.Map; + +public class FallbackTreatment { + private final Map _config; + private final String _treatment; + private final String _label; + + public FallbackTreatment(String treatment, Map config) { + _treatment = treatment; + _config = config; + _label = "fallback - "; + } + + public FallbackTreatment(String treatment) { + _treatment = treatment; + _config = null; + _label = "fallback - "; + } + + public Map getConfig() { + return _config; + } + + public String getTreatment() { + return _treatment; + } + + public String getLabel() { + return _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..dd38b228d --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java @@ -0,0 +1,25 @@ +package io.split.client.dtos; + +import java.util.Map; + +public class FallbackTreatmentsConfiguration { + private FallbackTreatment _globalFallbackTreatment; + private Map _byFlagFallbackTreatment; + + public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment, Map byFlagFallbackTreatment) { + _globalFallbackTreatment = globalFallbackTreatment; + _byFlagFallbackTreatment = byFlagFallbackTreatment; + } + + public FallbackTreatment getGlobalFallbackTreatment() { + return _globalFallbackTreatment; + } + public void setGlobalFallbackTreatment(FallbackTreatment newValue) { + _globalFallbackTreatment = newValue; + } + + public Map getByFlagFallbackTreatment() { return _byFlagFallbackTreatment;} + public void setByFlagFallbackTreatment(Map newValue) { + _byFlagFallbackTreatment = newValue; + } +} 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..d68f41f36 --- /dev/null +++ b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java @@ -0,0 +1,69 @@ +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 (!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; + } + + if (name.length() > MAX_LENGTH) { + return null; + } + + return name; + } + + public static Map isValidByFlagTreatment(Map byFlagTreatment, String method) { + Map result = new HashMap<>(); + for (Map.Entry entry : byFlagTreatment.entrySet()) { + Optional feature_name = isValid(entry.getKey(), "Validator"); + if (feature_name.equals(Optional.empty())) { + continue; + } + + FallbackTreatment fallbackTreatment = entry.getValue(); + String treatment = isValidTreatment(fallbackTreatment.getTreatment(), "Validator"); + if (treatment == null) { + continue; + } + + result.put(feature_name.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/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index bba824527..2f8844879 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -712,10 +712,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 +726,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(); 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")))); From 5f2084d59d43661251a8d8f349cbf75c0ec71ff0 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 4 Sep 2025 12:31:14 -0700 Subject: [PATCH 02/21] Updated evaluator --- .../io/split/client/SplitFactoryImpl.java | 7 +- .../java/io/split/client/utils/Utils.java | 28 +++++ .../split/engine/evaluator/EvaluatorImp.java | 19 ++-- .../io/split/client/SplitClientImplTest.java | 106 +++++++++--------- .../evaluator/EvaluatorIntegrationTest.java | 2 +- .../split/engine/evaluator/EvaluatorTest.java | 75 ++++++++++++- 6 files changed, 169 insertions(+), 68 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index cca655612..bcc1a9679 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -257,7 +257,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn config.getThreadFactory()); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); // SplitClient _client = new SplitClientImpl(this, @@ -348,7 +348,8 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata); UserCustomRuleBasedSegmentAdapterConsumer userCustomRuleBasedSegmentAdapterConsumer = new UserCustomRuleBasedSegmentAdapterConsumer(customStorageWrapper); - _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer); + _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, + userCustomRuleBasedSegmentAdapterConsumer, null); _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); _uniqueKeysTracker = createUniqueKeysTracker(config); _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, @@ -446,7 +447,7 @@ protected SplitFactoryImpl(SplitClientConfig config) { _impressionsManager, null, null, null); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); EventsStorage eventsStorage = new NoopEventsStorageImp(); diff --git a/client/src/main/java/io/split/client/utils/Utils.java b/client/src/main/java/io/split/client/utils/Utils.java index 9a386db55..74e17ee14 100644 --- a/client/src/main/java/io/split/client/utils/Utils.java +++ b/client/src/main/java/io/split/client/utils/Utils.java @@ -1,6 +1,8 @@ package io.split.client.utils; import io.split.client.dtos.ChangeDto; +import io.split.client.dtos.FallbackTreatmentsConfiguration; +import io.split.engine.evaluator.EvaluatorImp.TreatmentLabelAndChangeNumber; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; @@ -45,4 +47,30 @@ public static URI appendPath(URI root, String pathToAppend) throws URISyntaxExce public static boolean checkExitConditions(ChangeDto change, long cn) { return change.t < cn && change.t != -1; } + + public static TreatmentLabelAndChangeNumber checkFallbackTreatments(String treatment, String label, + String feature_name, Long changeNumber, + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { + if (fallbackTreatmentsConfiguration != null) { + if (fallbackTreatmentsConfiguration.getByFlagFallbackTreatment() != null + && fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name) != null + && !fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment().isEmpty()) { + return new TreatmentLabelAndChangeNumber( + fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment(), + fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getLabel() + label, + changeNumber); + } + + if (fallbackTreatmentsConfiguration.getGlobalFallbackTreatment() != null + && !fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment().isEmpty()) { + return new TreatmentLabelAndChangeNumber(fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment(), + fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getLabel() + label, + changeNumber); + } + } + + return new TreatmentLabelAndChangeNumber(treatment, + label, + changeNumber); + } } \ No newline at end of file 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..9dd718194 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,6 +1,7 @@ package io.split.engine.evaluator; import io.split.client.dtos.ConditionType; +import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -19,6 +20,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; +import static io.split.client.utils.Utils.checkFallbackTreatments; public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); @@ -26,19 +28,22 @@ public class EvaluatorImp implements Evaluator { private final SegmentCacheConsumer _segmentCacheConsumer; private final EvaluationContext _evaluationContext; private final SplitCacheConsumer _splitCacheConsumer; + private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache, - RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) { + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer, + FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { _splitCacheConsumer = checkNotNull(splitCacheConsumer); _segmentCacheConsumer = checkNotNull(segmentCache); _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer); + _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; } @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; } @@ -172,18 +177,18 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { } private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, - ParsedSplit parsedSplit) { + ParsedSplit parsedSplit, String feature_name) { try { if (parsedSplit == null) { - return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND); + return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, feature_name, null, _fallbackTreatmentsConfiguration); } 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()); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, e.changeNumber(), _fallbackTreatmentsConfiguration); } catch (Exception e) { _log.error("Evaluator Exception", e); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, null, _fallbackTreatmentsConfiguration); } } diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 5b56708d9..a5b55ed78 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -99,7 +99,7 @@ public void nullKeyResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment(null, "test1")); @@ -129,7 +129,7 @@ public void nullTestResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", null)); @@ -152,7 +152,7 @@ public void exceptionsResultInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test1")); @@ -184,7 +184,7 @@ public void works() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -222,7 +222,7 @@ public void worksNullConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); String randomKey = RandomStringUtils.random(10); @@ -258,7 +258,7 @@ public void worksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -295,7 +295,7 @@ public void lastConditionIsAlwaysDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -335,7 +335,7 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -371,7 +371,7 @@ public void multipleConditionsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -405,7 +405,7 @@ public void killedTestAlwaysGoesToDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -445,7 +445,7 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -483,7 +483,7 @@ public void dependencyMatcherOn() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -518,7 +518,7 @@ public void dependencyMatcherOff() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -547,7 +547,7 @@ public void dependencyMatcherControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -577,7 +577,7 @@ public void attributesWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -612,7 +612,7 @@ public void attributesWork2() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -648,7 +648,7 @@ public void attributesGreaterThanNegativeNumber() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -686,7 +686,7 @@ public void attributesForSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -731,7 +731,7 @@ public void labelsArePopulated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -834,7 +834,7 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -888,7 +888,7 @@ public void notInTrafficAllocationDefaultConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -932,7 +932,7 @@ public void matchingBucketingKeysWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -975,7 +975,7 @@ public void matchingBucketingKeysByFlagSetWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1016,7 +1016,7 @@ public void matchingBucketingKeysByFlagSetsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1054,7 +1054,7 @@ public void impressionMetadataIsPropagated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1097,7 +1097,7 @@ public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1119,7 +1119,7 @@ public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, Int NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1140,7 +1140,7 @@ public void trackWithValidParameters() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1166,7 +1166,7 @@ public void trackWithInvalidEventTypeIds() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Assert.assertFalse(client.track("validKey", "valid_traffic_type", "")); @@ -1191,7 +1191,7 @@ public void trackWithInvalidTrafficTypeNames() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1213,7 +1213,7 @@ public void trackWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1245,7 +1245,7 @@ public void getTreatmentWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Assert.assertNotEquals(Treatments.CONTROL, client.getTreatment("valid", "split")); @@ -1296,7 +1296,7 @@ public void trackWithProperties() { eventClientMock, config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1420,7 +1420,7 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1461,7 +1461,7 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1512,7 +1512,7 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1561,7 +1561,7 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1599,7 +1599,7 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1629,7 +1629,7 @@ public void nullKeyResultsInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatments(null, Collections.singletonList("test1")).get("test1")); @@ -1660,7 +1660,7 @@ public void nullSplitsResultsInEmptyGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(0, client.getTreatments("key", null).size()); @@ -1683,7 +1683,7 @@ public void exceptionsResultInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2")); @@ -1717,7 +1717,7 @@ public void getTreatmentsWorks() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("randomKey", Arrays.asList(test, "test2")); @@ -1748,7 +1748,7 @@ public void emptySplitsResultsInNullGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("key", new ArrayList<>()); @@ -1773,7 +1773,7 @@ public void exceptionsResultInControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1")); @@ -1811,7 +1811,7 @@ public void worksTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("anyKey", Arrays.asList(test, test2)); @@ -1849,7 +1849,7 @@ public void worksOneControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1894,7 +1894,7 @@ public void treatmentsWorksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -1937,7 +1937,7 @@ public void testTreatmentsByFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1978,7 +1978,7 @@ public void testTreatmentsByFlagSetInvalid() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", new HashMap<>()).isEmpty()); @@ -2022,7 +2022,7 @@ public void testTreatmentsByFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); int numKeys = 5; @@ -2078,7 +2078,7 @@ public void treatmentsWorksAndHasConfigFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -2135,7 +2135,7 @@ public void treatmentsWorksAndHasConfigFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -2181,7 +2181,7 @@ public void impressionPropertiesTest() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, new FlagSetsFilterImpl(new HashSet<>()) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); 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..e686f5308 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -174,7 +174,7 @@ 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); + Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); 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..c30209a9e 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, null); _matcher = Mockito.mock(CombiningMatcher.class); _evaluationContext = Mockito.mock(EvaluationContext.class); @@ -226,4 +224,73 @@ 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); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + + 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")); }} ); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + + 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")); }} ); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + + 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 From 455cf279d4475808f0b188572352f919944b5a72 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:09 -0700 Subject: [PATCH 03/21] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9dd718194..e08848eb2 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -177,7 +177,7 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { } private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, - ParsedSplit parsedSplit, String feature_name) { + ParsedSplit parsedSplit, String featureName) { try { if (parsedSplit == null) { return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, feature_name, null, _fallbackTreatmentsConfiguration); From 1f3e613ad767c4e2136514a91f5707ad02226aa8 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:15 -0700 Subject: [PATCH 04/21] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e08848eb2..4e192944b 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -180,7 +180,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St ParsedSplit parsedSplit, String featureName) { try { if (parsedSplit == null) { - return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, feature_name, null, _fallbackTreatmentsConfiguration); + return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, featureName, null, _fallbackTreatmentsConfiguration); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { From 6004e5508eb5c460b9a70167cb8e1f7006216fdf Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:22 -0700 Subject: [PATCH 05/21] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4e192944b..5dc28e6ec 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -185,7 +185,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, e.changeNumber(), _fallbackTreatmentsConfiguration); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, e.changeNumber(), _fallbackTreatmentsConfiguration); } catch (Exception e) { _log.error("Evaluator Exception", e); return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, null, _fallbackTreatmentsConfiguration); From cda0248787a019af7a64b78fd8d2d8e5e3c1374e Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:46:28 -0700 Subject: [PATCH 06/21] Update client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java Co-authored-by: nmayorsplit <104373752+nmayorsplit@users.noreply.github.com> --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5dc28e6ec..84445cd4a 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -188,7 +188,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, e.changeNumber(), _fallbackTreatmentsConfiguration); } catch (Exception e) { _log.error("Evaluator Exception", e); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, feature_name, null, _fallbackTreatmentsConfiguration); + return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, null, _fallbackTreatmentsConfiguration); } } From 6a7429222e93701e01e4c10bbe56039d69d725f5 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Fri, 5 Sep 2025 08:53:23 -0700 Subject: [PATCH 07/21] Update client/src/main/java/io/split/client/SplitClientConfig.java Co-authored-by: gthea --- client/src/main/java/io/split/client/SplitClientConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index 8dea4581a..f57acb6ab 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -1185,7 +1185,7 @@ private void verifyFallbackTreatments() { if (_fallbackTreatments.getGlobalFallbackTreatment() != null) { _fallbackTreatments.setGlobalFallbackTreatment(new FallbackTreatment( - isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "config"), + isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "Fallback treatments"), _fallbackTreatments.getGlobalFallbackTreatment().getConfig())); } From 0ba014ee505bed59989d7691c39592380072b2f5 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Fri, 5 Sep 2025 08:53:52 -0700 Subject: [PATCH 08/21] Update client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java Co-authored-by: gthea --- .../io/split/client/dtos/FallbackTreatmentsConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java index dd38b228d..4d74bc193 100644 --- a/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java @@ -3,8 +3,8 @@ import java.util.Map; public class FallbackTreatmentsConfiguration { - private FallbackTreatment _globalFallbackTreatment; - private Map _byFlagFallbackTreatment; + private final FallbackTreatment _globalFallbackTreatment; + private final Map _byFlagFallbackTreatment; public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment, Map byFlagFallbackTreatment) { _globalFallbackTreatment = globalFallbackTreatment; From 0815b1a7c56fb9f54d8e4e8f5e06b3c06755733b Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Fri, 5 Sep 2025 08:54:00 -0700 Subject: [PATCH 09/21] Update client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java Co-authored-by: gthea --- .../split/inputValidation/FallbackTreatmentValidator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java index d68f41f36..1a1718820 100644 --- a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java +++ b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java @@ -34,13 +34,13 @@ public static String isValidTreatment(String name, String method) { name = trimmed; } - 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)); + if (name.length() > MAX_LENGTH) { return null; } - if (name.length() > MAX_LENGTH) { + 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; } From 092207b2c19e890b0c860019c5e78240d98be5b7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 5 Sep 2025 09:07:41 -0700 Subject: [PATCH 10/21] polish --- .../main/java/io/split/client/SplitClientConfig.java | 11 ++++++++--- .../client/dtos/FallbackTreatmentsConfiguration.java | 6 ------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index f57acb6ab..e6e7a70af 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -18,6 +18,7 @@ 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; @@ -1183,15 +1184,19 @@ private void verifyFallbackTreatments() { if (_fallbackTreatments == null) return; + FallbackTreatment processedGlobalFallbackTreatment = _fallbackTreatments.getGlobalFallbackTreatment(); + Map processedByFlagFallbackTreatment = _fallbackTreatments.getByFlagFallbackTreatment(); + if (_fallbackTreatments.getGlobalFallbackTreatment() != null) { - _fallbackTreatments.setGlobalFallbackTreatment(new FallbackTreatment( + processedGlobalFallbackTreatment = new FallbackTreatment( isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "Fallback treatments"), - _fallbackTreatments.getGlobalFallbackTreatment().getConfig())); + _fallbackTreatments.getGlobalFallbackTreatment().getConfig()); } if (_fallbackTreatments.getByFlagFallbackTreatment() != null) { - _fallbackTreatments.setByFlagFallbackTreatment(isValidByFlagTreatment(_fallbackTreatments.getByFlagFallbackTreatment(), "config")); + processedByFlagFallbackTreatment = isValidByFlagTreatment(_fallbackTreatments.getByFlagFallbackTreatment(), "config"); } + _fallbackTreatments = new FallbackTreatmentsConfiguration(processedGlobalFallbackTreatment, processedByFlagFallbackTreatment); } public SplitClientConfig build() { diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java index 4d74bc193..aa47d1163 100644 --- a/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java @@ -14,12 +14,6 @@ public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment public FallbackTreatment getGlobalFallbackTreatment() { return _globalFallbackTreatment; } - public void setGlobalFallbackTreatment(FallbackTreatment newValue) { - _globalFallbackTreatment = newValue; - } public Map getByFlagFallbackTreatment() { return _byFlagFallbackTreatment;} - public void setByFlagFallbackTreatment(Map newValue) { - _byFlagFallbackTreatment = newValue; - } } From be872d251fe199cbc0240e85e7209a3c3276f754 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 5 Sep 2025 10:21:07 -0700 Subject: [PATCH 11/21] added fallback calculator --- .../split/client/dtos/FallbackTreatment.java | 10 ++++- .../dtos/FallbackTreatmentCalculator.java | 6 +++ .../dtos/FallbackTreatmentCalculatorImp.java | 40 +++++++++++++++++++ .../java/io/split/client/utils/Utils.java | 28 ------------- .../split/engine/evaluator/EvaluatorImp.java | 19 +++++---- .../FallbackTreatmentCalculationImpTest.java | 40 +++++++++++++++++++ .../split/engine/evaluator/EvaluatorTest.java | 9 +++-- 7 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculator.java create mode 100644 client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java create mode 100644 client/src/test/java/io/split/client/dtos/FallbackTreatmentCalculationImpTest.java diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java index c4f406e4a..542f90157 100644 --- a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java @@ -10,13 +10,19 @@ public class FallbackTreatment { public FallbackTreatment(String treatment, Map config) { _treatment = treatment; _config = config; - _label = "fallback - "; + _label = null; } public FallbackTreatment(String treatment) { _treatment = treatment; _config = null; - _label = "fallback - "; + _label = null; + } + + public FallbackTreatment(String treatment, Map config, String label) { + _treatment = treatment; + _config = config; + _label = label; } public Map getConfig() { 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..c9854d320 --- /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 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/utils/Utils.java b/client/src/main/java/io/split/client/utils/Utils.java index 74e17ee14..9a386db55 100644 --- a/client/src/main/java/io/split/client/utils/Utils.java +++ b/client/src/main/java/io/split/client/utils/Utils.java @@ -1,8 +1,6 @@ package io.split.client.utils; import io.split.client.dtos.ChangeDto; -import io.split.client.dtos.FallbackTreatmentsConfiguration; -import io.split.engine.evaluator.EvaluatorImp.TreatmentLabelAndChangeNumber; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; @@ -47,30 +45,4 @@ public static URI appendPath(URI root, String pathToAppend) throws URISyntaxExce public static boolean checkExitConditions(ChangeDto change, long cn) { return change.t < cn && change.t != -1; } - - public static TreatmentLabelAndChangeNumber checkFallbackTreatments(String treatment, String label, - String feature_name, Long changeNumber, - FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { - if (fallbackTreatmentsConfiguration != null) { - if (fallbackTreatmentsConfiguration.getByFlagFallbackTreatment() != null - && fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name) != null - && !fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment().isEmpty()) { - return new TreatmentLabelAndChangeNumber( - fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getTreatment(), - fallbackTreatmentsConfiguration.getByFlagFallbackTreatment().get(feature_name).getLabel() + label, - changeNumber); - } - - if (fallbackTreatmentsConfiguration.getGlobalFallbackTreatment() != null - && !fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment().isEmpty()) { - return new TreatmentLabelAndChangeNumber(fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getTreatment(), - fallbackTreatmentsConfiguration.getGlobalFallbackTreatment().getLabel() + label, - changeNumber); - } - } - - return new TreatmentLabelAndChangeNumber(treatment, - label, - changeNumber); - } } \ No newline at end of file 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 84445cd4a..773413b2b 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,6 +1,8 @@ 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.dtos.FallbackTreatmentsConfiguration; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.experiments.ParsedCondition; @@ -20,7 +22,6 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; -import static io.split.client.utils.Utils.checkFallbackTreatments; public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); @@ -28,15 +29,15 @@ public class EvaluatorImp implements Evaluator { private final SegmentCacheConsumer _segmentCacheConsumer; private final EvaluationContext _evaluationContext; private final SplitCacheConsumer _splitCacheConsumer; - private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; + private final FallbackTreatmentCalculator _fallbackTreatmentCalculator; public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache, RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer, - FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { + FallbackTreatmentCalculator fallbackTreatmentCalculator) { _splitCacheConsumer = checkNotNull(splitCacheConsumer); _segmentCacheConsumer = checkNotNull(segmentCache); _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer); - _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; + _fallbackTreatmentCalculator = fallbackTreatmentCalculator; } @Override @@ -179,16 +180,20 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, ParsedSplit parsedSplit, String featureName) { try { + if (parsedSplit == null) { - return checkFallbackTreatments(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND, featureName, null, _fallbackTreatmentsConfiguration); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.DEFINITION_NOT_FOUND); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel()); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); - return checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, e.changeNumber(), _fallbackTreatmentsConfiguration); + 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 checkFallbackTreatments(Treatments.CONTROL, Labels.EXCEPTION, featureName, null, _fallbackTreatmentsConfiguration); + FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.EXCEPTION); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel()); } } 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/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index c30209a9e..9ef7343aa 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -229,7 +229,8 @@ public void evaluateWithPrerequisites() { public void evaluateFallbackTreatmentWorks() { Mockito.when(_splitCacheConsumer.get(SPLIT_NAME)).thenReturn(null); FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + 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); @@ -245,7 +246,8 @@ public void evaluateFallbackTreatmentWorks() { 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")); }} ); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + 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); @@ -271,7 +273,8 @@ public void evaluateFallbackTreatmentWorks() { 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")); }} ); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, fallbackTreatmentsConfiguration); + 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); From 4af6f4f57c187d482b546de0963256f7f8d02b09 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 8 Sep 2025 14:07:52 -0700 Subject: [PATCH 12/21] Update client class --- .../java/io/split/client/SplitClientImpl.java | 44 +- .../io/split/client/SplitFactoryImpl.java | 22 +- .../split/engine/evaluator/EvaluatorImp.java | 38 +- .../io/split/client/SplitClientImplTest.java | 528 ++++++++++++++++-- .../evaluator/EvaluatorIntegrationTest.java | 4 +- .../split/engine/evaluator/EvaluatorTest.java | 2 +- 6 files changed, 548 insertions(+), 90 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 9f4b8ff9e..30bbea529 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,7 @@ * @author adil */ public final class SplitClientImpl implements SplitClient { - public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); +// 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 +63,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 +74,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 +86,7 @@ public SplitClientImpl(SplitFactory container, _telemetryEvaluationProducer = checkNotNull(telemetryEvaluationProducer); _telemetryConfigProducer = checkNotNull(telemetryConfigProducer); _flagSetsFilter = flagSetsFilter; + _fallbackTreatmentCalculator = fallbackTreatmentCalculator; } @Override @@ -492,31 +492,31 @@ 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()) { + if (result.label != null && result.label.contains(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; + return checkFallbackTreatment(featureFlag); } recordStats( @@ -541,10 +541,19 @@ 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().toString(); + } + return new SplitResult(fallbackTreatment.getTreatment(), config); + } + private String validateProperties(Map properties) { if (properties == null){ return null; @@ -563,6 +572,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); @@ -623,17 +633,17 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma return createMapControl(featureFlagNames); } } + 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()) { + if (evaluatorResult.get(t).label != null && evaluatorResult.get(t).label.contains(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); + result.put(t, checkFallbackTreatment(t)); } else { result.put(t, new SplitResult(evaluatorResult.get(t).treatment, evaluatorResult.get(t).configurations)); decoratedImpressions.add( @@ -735,7 +745,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 bcc1a9679..5d868f3ce 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(null); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); + _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,8 +352,9 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata); UserCustomRuleBasedSegmentAdapterConsumer userCustomRuleBasedSegmentAdapterConsumer = new UserCustomRuleBasedSegmentAdapterConsumer(customStorageWrapper); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, - userCustomRuleBasedSegmentAdapterConsumer, null); + userCustomRuleBasedSegmentAdapterConsumer, fallbackTreatmentCalculatorImp); _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); _uniqueKeysTracker = createUniqueKeysTracker(config); _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, @@ -378,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); @@ -446,8 +453,9 @@ protected SplitFactoryImpl(SplitClientConfig config) { SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp, _impressionsManager, null, null, null); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, null); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, fallbackTreatmentCalculatorImp); EventsStorage eventsStorage = new NoopEventsStorageImp(); @@ -461,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/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 773413b2b..282db10f7 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -3,12 +3,10 @@ import io.split.client.dtos.ConditionType; import io.split.client.dtos.FallbackTreatment; import io.split.client.dtos.FallbackTreatmentCalculator; -import io.split.client.dtos.FallbackTreatmentsConfiguration; 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; @@ -63,7 +61,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("Evaluator Exception", e); + return createMapControl(flagSetsWithNames, "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) { @@ -177,13 +195,25 @@ 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().toString(); + } + + return null; + } + private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, ParsedSplit parsedSplit, String featureName) { try { if (parsedSplit == null) { FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.DEFINITION_NOT_FOUND); - return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), fallbackTreatment.getLabel()); + return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), + fallbackTreatment.getLabel(), + null, + getFallbackConfig(fallbackTreatment), + true); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index a5b55ed78..9db75c0cc 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -100,7 +100,8 @@ public void nullKeyResultsInControl() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatment(null, "test1")); @@ -130,7 +131,8 @@ public void nullTestResultsInControl() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", null)); @@ -153,7 +155,8 @@ public void exceptionsResultInControl() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test1")); @@ -185,7 +188,8 @@ public void works() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -223,7 +227,8 @@ public void worksNullConfig() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); String randomKey = RandomStringUtils.random(10); SplitResult result = client.getTreatmentWithConfig(randomKey, test); @@ -259,7 +264,8 @@ public void worksAndHasConfig() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -296,7 +302,8 @@ public void lastConditionIsAlwaysDefault() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.OFF, client.getTreatment("pato@codigo.com", test)); @@ -336,7 +343,8 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); SplitResult result = client.getTreatmentWithConfig("pato@codigo.com", test); @@ -372,7 +380,8 @@ public void multipleConditionsWork() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("on", client.getTreatment("adil@codigo.com", test)); @@ -406,7 +415,8 @@ public void killedTestAlwaysGoesToDefault() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.OFF, client.getTreatment("adil@codigo.com", test)); @@ -446,7 +456,8 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); SplitResult result = client.getTreatmentWithConfig("adil@codigo.com", test); @@ -484,7 +495,8 @@ public void dependencyMatcherOn() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + 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, null), 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, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, fallbackTreatmentCalculatorImp), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + flagSetsFilter, + fallbackTreatmentCalculatorImp ); assertEquals(Treatments.ON, client.getTreatment("key", dependent)); @@ -578,7 +593,8 @@ public void attributesWork() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("on", client.getTreatment("adil@codigo.com", test)); @@ -613,7 +629,8 @@ public void attributesWork2() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); @@ -649,7 +666,8 @@ public void attributesGreaterThanNegativeNumber() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); @@ -687,7 +705,8 @@ public void attributesForSets() { config, gates, new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); @@ -732,7 +751,8 @@ public void labelsArePopulated() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -835,7 +855,8 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(expected_treatment_on_or_off, client.getTreatment(key, test)); @@ -889,7 +910,8 @@ public void notInTrafficAllocationDefaultConfig() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.OFF, client.getTreatment("pato@split.io", test)); @@ -933,7 +955,8 @@ public void matchingBucketingKeysWork() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Key bad_key = new Key("adil", "aijaz"); @@ -976,7 +999,8 @@ public void matchingBucketingKeysByFlagSetWork() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Key bad_key = new Key("adil", "aijaz"); @@ -1017,7 +1041,8 @@ public void matchingBucketingKeysByFlagSetsWork() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Key bad_key = new Key("adil", "aijaz"); @@ -1055,7 +1080,8 @@ public void impressionMetadataIsPropagated() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -1098,7 +1124,8 @@ public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, config, ready, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); client.blockUntilReady(); @@ -1120,7 +1147,8 @@ public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, Int config, ready, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); client.blockUntilReady(); @@ -1141,7 +1169,8 @@ public void trackWithValidParameters() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertTrue(client.track("validKey", "valid_traffic_type", "valid_event")); @@ -1167,7 +1196,8 @@ public void trackWithInvalidEventTypeIds() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertFalse(client.track("validKey", "valid_traffic_type", "")); Assert.assertFalse(client.track("validKey", "valid_traffic_type", null)); @@ -1192,7 +1222,8 @@ public void trackWithInvalidTrafficTypeNames() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertFalse(client.track("validKey", "", "valid")); @@ -1214,7 +1245,8 @@ public void trackWithInvalidKeys() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertFalse(client.track("", "valid_traffic_type", "valid")); @@ -1246,7 +1278,8 @@ public void getTreatmentWithInvalidKeys() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Assert.assertNotEquals(Treatments.CONTROL, client.getTreatment("valid", "split")); assertEquals(Treatments.CONTROL, client.getTreatment("", "split")); @@ -1297,7 +1330,8 @@ public void trackWithProperties() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); HashMap properties = new HashMap<>(); @@ -1421,7 +1455,8 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.ON, client.getTreatment("valid", "split")); @@ -1462,7 +1497,8 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1513,7 +1549,8 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1562,7 +1599,8 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1600,7 +1638,8 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); client.blockUntilReady(); @@ -1630,7 +1669,8 @@ public void nullKeyResultsInControlGetTreatments() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(Treatments.CONTROL, client.getTreatments(null, Collections.singletonList("test1")).get("test1")); @@ -1661,7 +1701,8 @@ public void nullSplitsResultsInEmptyGetTreatments() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertEquals(0, client.getTreatments("key", null).size()); @@ -1684,7 +1725,8 @@ public void exceptionsResultInControlGetTreatments() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + 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, null), 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)); @@ -1749,7 +1793,8 @@ public void emptySplitsResultsInNullGetTreatments() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map result = client.getTreatments("key", new ArrayList<>()); assertNotNull(result); @@ -1774,7 +1819,8 @@ public void exceptionsResultInControlTreatments() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1")); assertEquals(1, result.size()); @@ -1812,7 +1858,8 @@ public void worksTreatments() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + 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, null), 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, null), 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); @@ -1938,7 +1989,8 @@ public void testTreatmentsByFlagSet() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); int numKeys = 5; @@ -1979,7 +2031,8 @@ public void testTreatmentsByFlagSetInvalid() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + flagSetsFilter, + new FallbackTreatmentCalculatorImp(null) ); assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", new HashMap<>()).isEmpty()); } @@ -2023,7 +2076,8 @@ public void testTreatmentsByFlagSets() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - flagSetsFilter + 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, null), 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, null), 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); @@ -2182,7 +2240,9 @@ public void impressionPropertiesTest() { config, gates, new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer, null), TELEMETRY_STORAGE, TELEMETRY_STORAGE, - new FlagSetsFilterImpl(new HashSet<>()) + 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); + + Map fallbcakConfigGlobal = new HashMap() {{ put("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.toString(), 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.toString(), 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.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test1").config()); + assertEquals("on", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1").config()); + + Map fallbcakConfigByFlag = new HashMap() {{ put("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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals("off", client.getTreatmentWithConfig("adil@relateiq.com", "feature").treatment()); + assertEquals(fallbcakConfigByFlag.toString(), 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.toString(), results.get("feature").config()); + assertEquals("on", results.get("test").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), 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.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").config()); + assertEquals("off", client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").treatment()); + assertEquals(fallbcakConfigByFlag.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").config()); + assertEquals("on", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), 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.toString(), 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.toString(), 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.toString(), 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.toString(), 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.toString(), 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); + + Map fallbcakConfigGlobal = new HashMap() {{ put("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.toString(), 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.toString(), 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.toString(), 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.toString(), resultWithConfig.get("test2").config()); + + Map fallbcakConfigByFlag = new HashMap() {{ put("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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test3").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), 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.toString(), results.get("test2").config()); + assertEquals("on", results.get("test3").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), 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.toString(), results.get("test2").config()); + assertEquals("on", results.get("test3").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), 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.toString(), results.get("test2").config()); + assertEquals("on", results.get("test3").treatment()); + assertEquals(fallbcakConfigGlobal.toString(), 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.toString(), 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.toString(), 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.toString(), 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.toString(), 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/engine/evaluator/EvaluatorIntegrationTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java index e686f5308..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, null); + 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 9ef7343aa..05a87a611 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -48,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, null); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer, new FallbackTreatmentCalculatorImp(null)); _matcher = Mockito.mock(CombiningMatcher.class); _evaluationContext = Mockito.mock(EvaluationContext.class); From c26b3fa3e664e8e7a84d9d13a16331df92a53fdb Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 8 Sep 2025 14:19:49 -0700 Subject: [PATCH 13/21] polish --- client/src/main/java/io/split/client/SplitClientImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 30bbea529..0953bc710 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -46,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"; From a639d54f341758d3e7de60311c1dfedb6199fc5c Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 9 Sep 2025 10:01:23 -0700 Subject: [PATCH 14/21] polishing --- .../java/io/split/client/SplitClientImpl.java | 49 +++++++++---------- .../split/engine/evaluator/EvaluatorImp.java | 2 +- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 0953bc710..084baca0a 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -610,27 +610,22 @@ 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, @@ -638,18 +633,20 @@ private Map processEvaluatorResult(Map attributes, long initTime, String properties){ List decoratedImpressions = new ArrayList<>(); Map result = new HashMap<>(); - evaluatorResult.keySet().forEach(t -> { - if (evaluatorResult.get(t).label != null && evaluatorResult.get(t).label.contains(Labels.DEFINITION_NOT_FOUND) && _gates.isSDKReady()) { + evaluatorResult.keySet().forEach(flag -> { + if (evaluatorResult.get(flag).label != null && + evaluatorResult.get(flag).label.contains(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, checkFallbackTreatment(t)); + "what feature flags exist in the Split user interface.", methodEnum.getMethod(), flag)); + result.put(flag, checkFallbackTreatment(flag)); } else { - result.put(t, new SplitResult(evaluatorResult.get(t).treatment, evaluatorResult.get(t).configurations)); + result.put(flag, new SplitResult(evaluatorResult.get(flag).treatment, evaluatorResult.get(flag).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)); + new Impression(matchingKey, bucketingKey, flag, evaluatorResult.get(flag).treatment, System.currentTimeMillis(), + evaluatorResult.get(flag).label, evaluatorResult.get(flag).changeNumber, attributes, properties), + evaluatorResult.get(flag).track)); } }); _telemetryEvaluationProducer.recordLatency(methodEnum, System.currentTimeMillis() - initTime); 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 282db10f7..167d8c621 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -65,7 +65,7 @@ public Map evaluateFeaturesB return evaluateFeatures(key, bucketingKey, flagSetsWithNames, attributes); } catch (Exception e) { _log.error("Evaluator Exception", e); - return createMapControl(flagSetsWithNames, "exception"); + return createMapControl(flagSetsWithNames, io.split.engine.evaluator.Labels.EXCEPTION); } } From 480d1f31322230bf1f1adb7aff712c41bf3d0f77 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 9 Sep 2025 10:01:55 -0700 Subject: [PATCH 15/21] polishing --- .../src/main/java/io/split/engine/evaluator/EvaluatorImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 167d8c621..e74ea67fa 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -213,7 +213,7 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St fallbackTreatment.getLabel(), null, getFallbackConfig(fallbackTreatment), - true); + false); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { From 076a850625e16d571888a55879d02f46b86a3c30 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 9 Sep 2025 10:37:55 -0700 Subject: [PATCH 16/21] Convert fallback config to String --- .../java/io/split/client/SplitClientImpl.java | 2 +- .../split/client/dtos/FallbackTreatment.java | 10 ++- .../split/engine/evaluator/EvaluatorImp.java | 3 +- .../io/split/client/SplitClientImplTest.java | 72 +++++++++---------- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 084baca0a..32b68b092 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -548,7 +548,7 @@ private SplitResult checkFallbackTreatment(String featureName) { FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, ""); String config = null; if (fallbackTreatment.getConfig() != null) { - config = fallbackTreatment.getConfig().toString(); + config = fallbackTreatment.getConfig(); } return new SplitResult(fallbackTreatment.getTreatment(), config); } diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java index 542f90157..291db4f48 100644 --- a/client/src/main/java/io/split/client/dtos/FallbackTreatment.java +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatment.java @@ -1,13 +1,11 @@ package io.split.client.dtos; -import java.util.Map; - public class FallbackTreatment { - private final Map _config; + private final String _config; private final String _treatment; private final String _label; - public FallbackTreatment(String treatment, Map config) { + public FallbackTreatment(String treatment, String config) { _treatment = treatment; _config = config; _label = null; @@ -19,13 +17,13 @@ public FallbackTreatment(String treatment) { _label = null; } - public FallbackTreatment(String treatment, Map config, String label) { + public FallbackTreatment(String treatment, String config, String label) { _treatment = treatment; _config = config; _label = label; } - public Map getConfig() { + public String getConfig() { return _config; } 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 e74ea67fa..f3b06f8fa 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -197,7 +197,7 @@ private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) { private String getFallbackConfig(FallbackTreatment fallbackTreatment) { if (fallbackTreatment.getConfig() != null) { - return fallbackTreatment.getConfig().toString(); + return fallbackTreatment.getConfig(); } return null; @@ -206,7 +206,6 @@ private String getFallbackConfig(FallbackTreatment fallbackTreatment) { private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, String bucketingKey, Map attributes, ParsedSplit parsedSplit, String featureName) { try { - if (parsedSplit == null) { FallbackTreatment fallbackTreatment = _fallbackTreatmentCalculator.resolve(featureName, Labels.DEFINITION_NOT_FOUND); return new TreatmentLabelAndChangeNumber(fallbackTreatment.getTreatment(), diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 9db75c0cc..2556508c6 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -2306,7 +2306,7 @@ public void fallbackTreatmentWithExceptionsResult() { features.put("flag", new HashSet<>(Arrays.asList("test1"))); when(splitCacheConsumer.getNamesByFlagSets(anyList())).thenReturn(features); - Map fallbcakConfigGlobal = new HashMap() {{ put("prop1", "val1"); }}; + String fallbcakConfigGlobal = "{\"prop1\", \"val1\"}"; FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new FallbackTreatment("on", fallbcakConfigGlobal), null); @@ -2326,19 +2326,19 @@ public void fallbackTreatmentWithExceptionsResult() { ); assertEquals("on", client.getTreatment("adil@relateiq.com", "test1")); assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test1").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + 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.toString(), client.getTreatmentsWithConfig("adil@relateiq.com", Arrays.asList("test1")).get("test1").config()); + 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.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test1").config()); + 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.toString(), client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1").config()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test1").config()); - Map fallbcakConfigByFlag = new HashMap() {{ put("prop2", "val2"); }}; + String fallbcakConfigByFlag = "{\"prop2\", \"val2\"}"; fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on", fallbcakConfigGlobal), new HashMap() {{ put("feature", new FallbackTreatment("off", fallbcakConfigByFlag)); }}); @@ -2363,30 +2363,30 @@ public void fallbackTreatmentWithExceptionsResult() { 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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentWithConfig("adil@relateiq.com", "test1").config()); assertEquals("off", client.getTreatmentWithConfig("adil@relateiq.com", "feature").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "feature").config()); + 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.toString(), results.get("feature").config()); + assertEquals(fallbcakConfigByFlag, results.get("feature").config()); assertEquals("on", results.get("test").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), results.get("test").config()); + 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.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").config()); + assertEquals(fallbcakConfigGlobal, client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("test").config()); assertEquals("off", client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").config()); + 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.toString(), client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("test").config()); + 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.toString(), client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").config()); + 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)); }}); @@ -2409,13 +2409,13 @@ public void fallbackTreatmentWithExceptionsResult() { 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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "feature").config()); + 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.toString(), results.get("feature").config()); + assertEquals(fallbcakConfigByFlag, results.get("feature").config()); assertEquals(Treatments.CONTROL, results.get("test").treatment()); assertEquals(null, results.get("test").config()); @@ -2426,11 +2426,11 @@ public void fallbackTreatmentWithExceptionsResult() { 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.toString(), client.getTreatmentsWithConfigByFlagSet("adil@relateiq.com", "flag").get("feature").config()); + 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.toString(), client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").config()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").config()); } @Test @@ -2456,7 +2456,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { flagFeatures.put("flag", new HashSet<>(Arrays.asList("test1", "test2", "test3"))); when(splitCacheConsumer.getNamesByFlagSets(anyList())).thenReturn(flagFeatures); - Map fallbcakConfigGlobal = new HashMap() {{ put("prop1", "val1"); }}; + String fallbcakConfigGlobal = "{\"prop1\", \"val1\"}"; FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration( new FallbackTreatment("on", fallbcakConfigGlobal), null); @@ -2477,7 +2477,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { 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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + 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")); @@ -2486,7 +2486,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", resultWithConfig.get("test1").treatment()); assertEquals(null, resultWithConfig.get("test1").config()); assertEquals("on", resultWithConfig.get("test2").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), resultWithConfig.get("test2").config()); + assertEquals(fallbcakConfigGlobal, resultWithConfig.get("test2").config()); result = client.getTreatmentsByFlagSet("adil@relateiq.com", "flag"); assertEquals("off", result.get("test1")); @@ -2498,14 +2498,14 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", resultWithConfig.get("test1").treatment()); assertEquals(null, resultWithConfig.get("test1").config()); assertEquals("on", resultWithConfig.get("test2").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), resultWithConfig.get("test2").config()); + 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.toString(), resultWithConfig.get("test2").config()); + assertEquals(fallbcakConfigGlobal, resultWithConfig.get("test2").config()); - Map fallbcakConfigByFlag = new HashMap() {{ put("prop2", "val2"); }}; + String fallbcakConfigByFlag = "{\"prop2\", \"val2\"}"; fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on", fallbcakConfigGlobal), new HashMap() {{ put("test2", new FallbackTreatment("off-fallback", fallbcakConfigByFlag)); }}); @@ -2530,9 +2530,9 @@ public void fallbackTreatmentWithSplitNotFoundResult() { 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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + assertEquals(fallbcakConfigByFlag, client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); assertEquals("on", client.getTreatmentWithConfig("adil@relateiq.com", "test3").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test3").config()); + 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")); @@ -2543,9 +2543,9 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", results.get("test1").treatment()); assertEquals(null, results.get("test1").config()); assertEquals("off-fallback", results.get("test2").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), results.get("test2").config()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); assertEquals("on", results.get("test3").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), results.get("test3").config()); + assertEquals(fallbcakConfigGlobal, results.get("test3").config()); result = client.getTreatmentsByFlagSet("adil@relateiq.com", "flag"); assertEquals("off", result.get("test1")); @@ -2561,17 +2561,17 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", results.get("test1").treatment()); assertEquals(null, results.get("test1").config()); assertEquals("off-fallback", results.get("test2").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), results.get("test2").config()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); assertEquals("on", results.get("test3").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), results.get("test3").config()); + 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.toString(), results.get("test2").config()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); assertEquals("on", results.get("test3").treatment()); - assertEquals(fallbcakConfigGlobal.toString(), results.get("test3").config()); + assertEquals(fallbcakConfigGlobal, results.get("test3").config()); fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null, new HashMap() {{ put("test2", new FallbackTreatment("off-fallback", fallbcakConfigByFlag)); }}); @@ -2596,7 +2596,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { 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.toString(), client.getTreatmentWithConfig("adil@relateiq.com", "test2").config()); + 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()); @@ -2609,7 +2609,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", results.get("test1").treatment()); assertEquals(null, results.get("test1").config()); assertEquals("off-fallback", results.get("test2").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), results.get("test2").config()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); assertEquals(Treatments.CONTROL, results.get("test3").treatment()); assertEquals(null, results.get("test3").config()); @@ -2627,7 +2627,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", results.get("test1").treatment()); assertEquals(null, results.get("test1").config()); assertEquals("off-fallback", results.get("test2").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), results.get("test2").config()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); assertEquals(Treatments.CONTROL, results.get("test3").treatment()); assertEquals(null, results.get("test3").config()); @@ -2635,7 +2635,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() { assertEquals("off", results.get("test1").treatment()); assertEquals(null, results.get("test1").config()); assertEquals("off-fallback", results.get("test2").treatment()); - assertEquals(fallbcakConfigByFlag.toString(), results.get("test2").config()); + assertEquals(fallbcakConfigByFlag, results.get("test2").config()); assertEquals(Treatments.CONTROL, results.get("test3").treatment()); assertEquals(null, results.get("test3").config()); } From f29078ebf8ded055b808d5829394c6c456797cb6 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 9 Sep 2025 12:08:12 -0700 Subject: [PATCH 17/21] Updated Factory --- .../io/split/client/SplitFactoryImpl.java | 6 +- .../client/SplitClientIntegrationTest.java | 218 ++++++++++++++++++ .../io/split/client/SplitFactoryImplTest.java | 33 +++ 3 files changed, 254 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 5d868f3ce..9ad38ef1b 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -257,7 +257,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory()); - FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(config.fallbackTreatments()); // Evaluator _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, fallbackTreatmentCalculatorImp); @@ -352,7 +352,7 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata); UserCustomRuleBasedSegmentAdapterConsumer userCustomRuleBasedSegmentAdapterConsumer = new UserCustomRuleBasedSegmentAdapterConsumer(customStorageWrapper); - FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(config.fallbackTreatments()); _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer, fallbackTreatmentCalculatorImp); _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); @@ -453,7 +453,7 @@ protected SplitFactoryImpl(SplitClientConfig config) { SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp, _impressionsManager, null, null, null); - FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(null); + FallbackTreatmentCalculatorImp fallbackTreatmentCalculatorImp = new FallbackTreatmentCalculatorImp(config.fallbackTreatments()); // Evaluator _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache, fallbackTreatmentCalculatorImp); diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 2f8844879..042290395 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; @@ -1175,6 +1177,222 @@ 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); + } + 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 From 567d2f1a934c6317e9dbdd1dc4a5221b70f10ebe Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 10 Sep 2025 12:27:19 -0700 Subject: [PATCH 18/21] Added label not ready for fallback --- .../java/io/split/client/SplitClientImpl.java | 44 ++++++----- .../io/split/engine/evaluator/Labels.java | 1 + .../client/SplitClientIntegrationTest.java | 77 +++++++++++++++++++ 3 files changed, 103 insertions(+), 19 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 32b68b092..38ab5e6ba 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -510,12 +510,15 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu long start = System.currentTimeMillis(); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, featureFlag, attributes); - - if (result.label != null && result.label.contains(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 checkFallbackTreatment(featureFlag); + 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( @@ -525,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, @@ -634,20 +637,23 @@ private Map processEvaluatorResult(Map decoratedImpressions = new ArrayList<>(); Map result = new HashMap<>(); 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) && - _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)); - } else { - 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(), - evaluatorResult.get(flag).label, evaluatorResult.get(flag).changeNumber, attributes, properties), - evaluatorResult.get(flag).track)); + 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()) { 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/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 042290395..4bdbc598d 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -1393,6 +1393,83 @@ public MockResponse dispatch(RecordedRequest request) { 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)) { From f93b8b11dd336e55aa6789506bd4c2b15cab09f7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 12 Sep 2025 08:01:12 -0700 Subject: [PATCH 19/21] Updated version and changes --- CHANGES.txt | 3 +++ client/pom.xml | 4 ++-- okhttp-modules/pom.xml | 4 ++-- pluggable-storage/pom.xml | 2 +- pom.xml | 2 +- redis-wrapper/pom.xml | 2 +- testing/pom.xml | 4 ++-- 7 files changed, 12 insertions(+), 9 deletions(-) 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/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 From 5a87a2e624abb77737d4ca9baeabde4c04b5c531 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 12 Sep 2025 10:43:58 -0700 Subject: [PATCH 20/21] polish --- .../client/dtos/FallbackTreatmentCalculatorImp.java | 2 +- .../java/io/split/engine/evaluator/EvaluatorImp.java | 7 ++++--- .../inputValidation/FallbackTreatmentValidator.java | 12 +++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java index c9854d320..936abc493 100644 --- a/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentCalculatorImp.java @@ -5,7 +5,7 @@ public class FallbackTreatmentCalculatorImp implements FallbackTreatmentCalculator { private final FallbackTreatmentsConfiguration _fallbackTreatmentsConfiguration; - private final String labelPrefix = "fallback - "; + private final static String labelPrefix = "fallback - "; public FallbackTreatmentCalculatorImp(FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration) { _fallbackTreatmentsConfiguration = fallbackTreatmentsConfiguration; 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 f3b06f8fa..8d7147aa6 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -28,6 +28,7 @@ public class EvaluatorImp implements Evaluator { 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, @@ -64,7 +65,7 @@ public Map evaluateFeaturesB try { return evaluateFeatures(key, bucketingKey, flagSetsWithNames, attributes); } catch (Exception e) { - _log.error("Evaluator Exception", e); + _log.error(_evaluatorException, e); return createMapControl(flagSetsWithNames, io.split.engine.evaluator.Labels.EXCEPTION); } } @@ -216,11 +217,11 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { - _log.error("Evaluator Exception", e.wrappedException()); + _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); + _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/inputValidation/FallbackTreatmentValidator.java b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java index 1a1718820..6142424ae 100644 --- a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java +++ b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java @@ -50,18 +50,16 @@ public static String isValidTreatment(String name, String method) { public static Map isValidByFlagTreatment(Map byFlagTreatment, String method) { Map result = new HashMap<>(); for (Map.Entry entry : byFlagTreatment.entrySet()) { - Optional feature_name = isValid(entry.getKey(), "Validator"); - if (feature_name.equals(Optional.empty())) { + Optional featureName = isValid(entry.getKey(), method); + if (featureName.equals(Optional.empty())) { continue; } FallbackTreatment fallbackTreatment = entry.getValue(); - String treatment = isValidTreatment(fallbackTreatment.getTreatment(), "Validator"); - if (treatment == null) { - continue; + String treatment = isValidTreatment(fallbackTreatment.getTreatment(), method); + if (treatment != null) { + result.put(featureName.get(), new FallbackTreatment(treatment, fallbackTreatment.getConfig())); } - - result.put(feature_name.get(), new FallbackTreatment(treatment, fallbackTreatment.getConfig())); } return result; From fb169e8c6e88844da82ab5af74e482a8d26f592c Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 12 Sep 2025 10:51:15 -0700 Subject: [PATCH 21/21] polish --- .../io/split/inputValidation/FallbackTreatmentValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java index 6142424ae..9fafe5eea 100644 --- a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java +++ b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java @@ -51,7 +51,7 @@ public static Map isValidByFlagTreatment(Map result = new HashMap<>(); for (Map.Entry entry : byFlagTreatment.entrySet()) { Optional featureName = isValid(entry.getKey(), method); - if (featureName.equals(Optional.empty())) { + if (featureName.equals(Optional.empty()) || !featureName.isPresent()) { continue; }