diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index f32b9b091..e6e7a70af 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -1,5 +1,7 @@ package io.split.client; +import io.split.client.dtos.FallbackTreatment; +import io.split.client.dtos.FallbackTreatmentsConfiguration; import io.split.client.dtos.ProxyConfiguration; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; @@ -16,10 +18,13 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.concurrent.ThreadFactory; import java.io.InputStream; +import static io.split.inputValidation.FallbackTreatmentValidator.isValidByFlagTreatment; +import static io.split.inputValidation.FallbackTreatmentValidator.isValidTreatment; import static io.split.inputValidation.FlagSetsValidator.cleanup; /** @@ -91,6 +96,7 @@ private HttpScheme() { private final CustomStorageWrapper _customStorageWrapper; private final StorageMode _storageMode; private final ThreadFactory _threadFactory; + private final FallbackTreatmentsConfiguration _fallbackTreatments; // Proxy configs private final ProxyConfiguration _proxyConfiguration; @@ -163,7 +169,8 @@ private SplitClientConfig(String endpoint, HashSet flagSetsFilter, int invalidSets, CustomHeaderDecorator customHeaderDecorator, - CustomHttpModule alternativeHTTPModule) { + CustomHttpModule alternativeHTTPModule, + FallbackTreatmentsConfiguration fallbackTreatments) { _endpoint = endpoint; _eventsEndpoint = eventsEndpoint; _featuresRefreshRate = pollForFeatureChangesEveryNSeconds; @@ -218,6 +225,7 @@ private SplitClientConfig(String endpoint, _invalidSets = invalidSets; _customHeaderDecorator = customHeaderDecorator; _alternativeHTTPModule = alternativeHTTPModule; + _fallbackTreatments = fallbackTreatments; Properties props = new Properties(); try { @@ -436,6 +444,8 @@ public boolean isSdkEndpointOverridden() { public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; } + public FallbackTreatmentsConfiguration fallbackTreatments() { return _fallbackTreatments; } + public static final class Builder { private String _endpoint = SDK_ENDPOINT; private boolean _endpointSet = false; @@ -494,6 +504,7 @@ public static final class Builder { private int _invalidSetsCount = 0; private CustomHeaderDecorator _customHeaderDecorator = null; private CustomHttpModule _alternativeHTTPModule = null; + private FallbackTreatmentsConfiguration _fallbackTreatments; public Builder() { } @@ -1022,6 +1033,17 @@ public Builder alternativeHTTPModule(CustomHttpModule alternativeHTTPModule) { return this; } + /** + * Fallback Treatments + * + * @param fallbackTreatments + * @return this builder + */ + public Builder fallbackTreatments(FallbackTreatmentsConfiguration fallbackTreatments) { + _fallbackTreatments = fallbackTreatments; + return this; + } + /** * Thread Factory * @@ -1158,6 +1180,25 @@ private void verifyProxy() { } } + private void verifyFallbackTreatments() { + if (_fallbackTreatments == null) + return; + + FallbackTreatment processedGlobalFallbackTreatment = _fallbackTreatments.getGlobalFallbackTreatment(); + Map processedByFlagFallbackTreatment = _fallbackTreatments.getByFlagFallbackTreatment(); + + if (_fallbackTreatments.getGlobalFallbackTreatment() != null) { + processedGlobalFallbackTreatment = new FallbackTreatment( + isValidTreatment(_fallbackTreatments.getGlobalFallbackTreatment().getTreatment(), "Fallback treatments"), + _fallbackTreatments.getGlobalFallbackTreatment().getConfig()); + } + + if (_fallbackTreatments.getByFlagFallbackTreatment() != null) { + processedByFlagFallbackTreatment = isValidByFlagTreatment(_fallbackTreatments.getByFlagFallbackTreatment(), "config"); + } + _fallbackTreatments = new FallbackTreatmentsConfiguration(processedGlobalFallbackTreatment, processedByFlagFallbackTreatment); + } + public SplitClientConfig build() { verifyRates(); @@ -1172,6 +1213,8 @@ public SplitClientConfig build() { verifyProxy(); + verifyFallbackTreatments(); + if (_numThreadsForSegmentFetch <= 0) { throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero"); } @@ -1230,7 +1273,8 @@ public SplitClientConfig build() { _flagSetsFilter, _invalidSetsCount, _customHeaderDecorator, - _alternativeHTTPModule); + _alternativeHTTPModule, + _fallbackTreatments); } } } \ No newline at end of file diff --git a/client/src/main/java/io/split/client/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..aa47d1163 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/FallbackTreatmentsConfiguration.java @@ -0,0 +1,19 @@ +package io.split.client.dtos; + +import java.util.Map; + +public class FallbackTreatmentsConfiguration { + private final FallbackTreatment _globalFallbackTreatment; + private final Map _byFlagFallbackTreatment; + + public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment, Map byFlagFallbackTreatment) { + _globalFallbackTreatment = globalFallbackTreatment; + _byFlagFallbackTreatment = byFlagFallbackTreatment; + } + + public FallbackTreatment getGlobalFallbackTreatment() { + return _globalFallbackTreatment; + } + + public Map getByFlagFallbackTreatment() { return _byFlagFallbackTreatment;} +} diff --git a/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java b/client/src/main/java/io/split/inputValidation/FallbackTreatmentValidator.java new file mode 100644 index 000000000..1a1718820 --- /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 (name.length() > MAX_LENGTH) { + return null; + } + + if (!TREATMENT_MATCHER.matcher(name).find()) { + _log.error(String.format("%s: you passed %s, treatment must adhere to the regular expression " + + "^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$", method, name)); + return null; + } + + return name; + } + + public static Map isValidByFlagTreatment(Map byFlagTreatment, String method) { + Map result = new HashMap<>(); + for (Map.Entry entry : byFlagTreatment.entrySet()) { + Optional 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"))));