Repository: Unleash/unleash-client-java
Pull request #83 (2019-07-30 10:53:03)

feat: add constraint support
his is an initial implementation of context support. Currently it is guarded behind the "flexibleRollout" strategy. General support for constraints for all strategies will be introduced in v. 4.x.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/24874128/badge)](https://coveralls.io/builds/24874128)

Coverage decreased (-0.05%) to 93.566% when pulling **faa98ea553e8230f09bc383cec81564c7b609327 on feat/constraints** into **5e70fcbb6b7dbee951aa1b89359e74094c800b82 on master**.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 013cbe93..66c4ec0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## 3.2.8
+- feat: Add support for static context fields (#82)
+
 ## 3.2.7
 - fix: Bump log4j to version 2.11.2.
 
diff --git a/src/main/java/no/finn/unleash/ActivationStrategy.java b/src/main/java/no/finn/unleash/ActivationStrategy.java
index 039c2107..78063afb 100644
--- a/src/main/java/no/finn/unleash/ActivationStrategy.java
+++ b/src/main/java/no/finn/unleash/ActivationStrategy.java
@@ -1,14 +1,22 @@
 package no.finn.unleash;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 public final class ActivationStrategy {
     private final String name;
     private final Map<String, String> parameters;
+    private final List<Constraint> constraints;
 
     public ActivationStrategy(String name, Map<String, String> parameters) {
+        this(name, parameters, Collections.emptyList());
+    }
+
+    public ActivationStrategy(String name, Map<String, String> parameters, List<Constraint> constraints) {
         this.name = name;
         this.parameters = parameters;
+        this.constraints = constraints;
     }
 
     public String getName() {
@@ -18,4 +26,8 @@ public final class ActivationStrategy {
     public Map<String, String> getParameters() {
         return parameters;
     }
+
+    public List<Constraint> getConstraints() {
+        return constraints;
+    }
 }
diff --git a/src/main/java/no/finn/unleash/Constraint.java b/src/main/java/no/finn/unleash/Constraint.java
new file mode 100644
index 00000000..3cfb4934
--- /dev/null
+++ b/src/main/java/no/finn/unleash/Constraint.java
@@ -0,0 +1,27 @@
+package no.finn.unleash;
+
+import java.util.List;
+
+public class Constraint {
+    private final String contextName;
+    private final Operator operator;
+    private final List<String> values;
+
+    public Constraint(String contextName, Operator operator, List<String> values) {
+        this.contextName = contextName;
+        this.operator = operator;
+        this.values = values;
+    }
+
+    public String getContextName() {
+        return contextName;
+    }
+
+    public Operator getOperator() {
+        return operator;
+    }
+
+    public List<String> getValues() {
+        return values;
+    }
+}
diff --git a/src/main/java/no/finn/unleash/DefaultUnleash.java b/src/main/java/no/finn/unleash/DefaultUnleash.java
index 6590f244..f7ee726c 100644
--- a/src/main/java/no/finn/unleash/DefaultUnleash.java
+++ b/src/main/java/no/finn/unleash/DefaultUnleash.java
@@ -14,15 +14,7 @@ import no.finn.unleash.repository.FeatureToggleRepository;
 import no.finn.unleash.repository.HttpToggleFetcher;
 import no.finn.unleash.repository.ToggleBackupHandlerFile;
 import no.finn.unleash.repository.ToggleRepository;
-import no.finn.unleash.strategy.ApplicationHostnameStrategy;
-import no.finn.unleash.strategy.DefaultStrategy;
-import no.finn.unleash.strategy.GradualRolloutRandomStrategy;
-import no.finn.unleash.strategy.GradualRolloutSessionIdStrategy;
-import no.finn.unleash.strategy.GradualRolloutUserIdStrategy;
-import no.finn.unleash.strategy.RemoteAddressStrategy;
-import no.finn.unleash.strategy.Strategy;
-import no.finn.unleash.strategy.UnknownStrategy;
-import no.finn.unleash.strategy.UserWithIdStrategy;
+import no.finn.unleash.strategy.*;
 import no.finn.unleash.util.UnleashConfig;
 
 import static java.util.Optional.ofNullable;
@@ -36,7 +28,8 @@ public final class DefaultUnleash implements Unleash {
             new GradualRolloutSessionIdStrategy(),
             new GradualRolloutUserIdStrategy(),
             new RemoteAddressStrategy(),
-            new UserWithIdStrategy());
+            new UserWithIdStrategy(),
+            new FlexibleRolloutStrategy());
 
     private static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy();
 
@@ -100,7 +93,7 @@ public final class DefaultUnleash implements Unleash {
         } else {
             UnleashContext enhancedContext = context.applyStaticFields(config);
             enabled = featureToggle.getStrategies().stream()
-                    .anyMatch(as -> getStrategy(as.getName()).isEnabled(as.getParameters(), enhancedContext));
+                    .anyMatch(as -> getStrategy(as.getName()).isEnabled(as.getParameters(), enhancedContext, as.getConstraints()));
         }
         return enabled;
     }
diff --git a/src/main/java/no/finn/unleash/Operator.java b/src/main/java/no/finn/unleash/Operator.java
new file mode 100644
index 00000000..92f8ea69
--- /dev/null
+++ b/src/main/java/no/finn/unleash/Operator.java
@@ -0,0 +1,6 @@
+package no.finn.unleash;
+
+public enum Operator {
+    IN,
+    NOT_IN
+}
diff --git a/src/main/java/no/finn/unleash/UnleashContext.java b/src/main/java/no/finn/unleash/UnleashContext.java
index 7a2b67f9..e5216997 100644
--- a/src/main/java/no/finn/unleash/UnleashContext.java
+++ b/src/main/java/no/finn/unleash/UnleashContext.java
@@ -52,6 +52,17 @@ public class UnleashContext {
         return environment;
     }
 
+    public Optional<String> getByName(String contextName) {
+        switch (contextName) {
+            case "environment": return environment;
+            case "appName": return appName;
+            case "userId": return userId;
+            case "sessionId": return sessionId;
+            case "remoteAddress": return remoteAddress;
+            default: return Optional.ofNullable(properties.get(contextName));
+        }
+    }
+
     public UnleashContext applyStaticFields(UnleashConfig config) {
         Builder builder = new Builder(this);
         if(!this.environment.isPresent()) {
diff --git a/src/main/java/no/finn/unleash/strategy/ConstraintUtil.java b/src/main/java/no/finn/unleash/strategy/ConstraintUtil.java
new file mode 100644
index 00000000..c1606857
--- /dev/null
+++ b/src/main/java/no/finn/unleash/strategy/ConstraintUtil.java
@@ -0,0 +1,25 @@
+package no.finn.unleash.strategy;
+
+import java.util.List;
+import java.util.Optional;
+
+import no.finn.unleash.Constraint;
+import no.finn.unleash.Operator;
+import no.finn.unleash.UnleashContext;
+
+public class ConstraintUtil {
+
+    public static boolean validate(List<Constraint> constraints, UnleashContext context) {
+        if(constraints != null && constraints.size() > 0) {
+            return constraints.stream().allMatch(c -> validateConstraint(c, context));
+        } else {
+            return true;
+        }
+    }
+
+    private static boolean validateConstraint(Constraint constraint, UnleashContext context) {
+        Optional<String> contextValue = context.getByName(constraint.getContextName());
+        boolean isIn = contextValue.isPresent() && constraint.getValues().contains(contextValue.get().trim());
+        return (constraint.getOperator() == Operator.IN) == isIn;
+    }
+}
diff --git a/src/main/java/no/finn/unleash/strategy/FlexibleRolloutStrategy.java b/src/main/java/no/finn/unleash/strategy/FlexibleRolloutStrategy.java
new file mode 100644
index 00000000..dad17d4d
--- /dev/null
+++ b/src/main/java/no/finn/unleash/strategy/FlexibleRolloutStrategy.java
@@ -0,0 +1,65 @@
+package no.finn.unleash.strategy;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import no.finn.unleash.UnleashContext;
+
+public class FlexibleRolloutStrategy implements Strategy {
+    protected static final String PERCENTAGE = "rollout";
+    protected static final String GROUP_ID = "groupId";
+
+    private Supplier<String> randomGenerator;
+
+    public FlexibleRolloutStrategy() {
+        this.randomGenerator = () ->  Math.random() * 100 + "";
+    }
+
+    public FlexibleRolloutStrategy(Supplier<String> randomGenerator) {
+        this.randomGenerator = randomGenerator;
+    }
+
+    @Override
+    public String getName() {
+        return "flexibleRollout";
+    }
+
+    @Override
+    public boolean isEnabled(Map<String, String> parameters) {
+        return false;
+    }
+
+    private Optional<String> resolveStickiness(String stickiness, UnleashContext context) {
+        switch (stickiness) {
+            case "userId": return context.getUserId();
+            case "sessionId": return context.getSessionId();
+            case "random": return Optional.of(randomGenerator.get());
+            default:
+                String value = context.getUserId()
+                        .orElse(context.getSessionId()
+                        .orElse(this.randomGenerator.get()));
+                return Optional.of(value);
+        }
+    }
+
+    @Override
+    public boolean isEnabled(Map<String, String> parameters, UnleashContext unleashContext) {
+        final String stickiness = getStickiness(parameters);
+        final Optional<String> stickinessId = resolveStickiness(stickiness, unleashContext);
+        final int percentage = StrategyUtils.getPercentage(parameters.get(PERCENTAGE));
+        final String groupId = Optional.ofNullable(parameters.get(GROUP_ID)).orElse("");
+
+        if(stickinessId.isPresent()) {
+            final int normalizedUserId = StrategyUtils.getNormalizedNumber(stickinessId.get(), groupId);
+            return percentage > 0 && normalizedUserId <= percentage;
+        } else {
+            return false;
+        }
+    }
+
+    private String getStickiness(Map<String, String> parameters) {
+        Optional<String> stickiness = Optional.ofNullable(parameters.get("stickiness"));
+        return stickiness.orElse("default");
+    }
+}
diff --git a/src/main/java/no/finn/unleash/strategy/Strategy.java b/src/main/java/no/finn/unleash/strategy/Strategy.java
index 24d2b3c8..a52dd8c9 100644
--- a/src/main/java/no/finn/unleash/strategy/Strategy.java
+++ b/src/main/java/no/finn/unleash/strategy/Strategy.java
@@ -1,6 +1,9 @@
 package no.finn.unleash.strategy;
 
+import java.util.List;
 import java.util.Map;
+
+import no.finn.unleash.Constraint;
 import no.finn.unleash.UnleashContext;
 
 public interface Strategy {
@@ -11,4 +14,8 @@ public interface Strategy {
     default boolean isEnabled(Map<String, String> parameters, UnleashContext unleashContext) {
         return isEnabled(parameters);
     }
+
+    default boolean isEnabled(Map<String, String> parameters, UnleashContext unleashContext, List<Constraint> constraints) {
+        return ConstraintUtil.validate(constraints, unleashContext) && isEnabled(parameters, unleashContext);
+    }
 }


Test patch:
diff --git a/src/test/java/no/finn/unleash/UnleashTest.java b/src/test/java/no/finn/unleash/UnleashTest.java
index bfc3bff8..8f9f6430 100644
--- a/src/test/java/no/finn/unleash/UnleashTest.java
+++ b/src/test/java/no/finn/unleash/UnleashTest.java
@@ -1,5 +1,6 @@
 package no.finn.unleash;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -45,6 +46,7 @@ public class UnleashTest {
         UnleashConfig config = new UnleashConfig.Builder()
                 .appName("test")
                 .unleashAPI("http://localhost:4242/api/")
+                .environment("test")
                 .unleashContextProvider(contextProvider)
                 .build();
 
@@ -95,7 +97,7 @@ public class UnleashTest {
 
         unleash.isEnabled("test");
 
-        verify(customStrategy, times(1)).isEnabled(isNull(), any(UnleashContext.class));
+        verify(customStrategy, times(1)).isEnabled(isNull(), any(UnleashContext.class), any(List.class));
     }
 
     @Test
@@ -343,6 +345,33 @@ public class UnleashTest {
         assertThat(result.isEnabled(), is(true));
     }
 
+    @Test
+    public void should_be_enabled_with_strategy_constraints() {
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.IN, Arrays.asList("test")));
+        ActivationStrategy activeStrategy = new ActivationStrategy("default", null, constraints);
+
+        FeatureToggle featureToggle = new FeatureToggle("test", true, asList(activeStrategy));
+
+        when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+
+        assertThat(unleash.isEnabled("test"), is(true));
+    }
+
+    @Test
+    public void should_be_disabled_with_strategy_constraints() {
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.IN, Arrays.asList("dev", "prod")));
+        ActivationStrategy activeStrategy = new ActivationStrategy("default", null, constraints);
+
+        FeatureToggle featureToggle = new FeatureToggle("test", true, asList(activeStrategy));
+
+        when(toggleRepository.getToggle("test")).thenReturn(featureToggle);
+
+        assertThat(unleash.isEnabled("test"), is(false));
+    }
+
+
     private List<VariantDefinition> getTestVariants() {
         return asList(
             new VariantDefinition("en", 50, new Payload("string", "en"), Collections.emptyList()),
diff --git a/src/test/java/no/finn/unleash/strategy/FlexibleRolloutStrategyTest.java b/src/test/java/no/finn/unleash/strategy/FlexibleRolloutStrategyTest.java
new file mode 100644
index 00000000..fc60bb15
--- /dev/null
+++ b/src/test/java/no/finn/unleash/strategy/FlexibleRolloutStrategyTest.java
@@ -0,0 +1,147 @@
+package no.finn.unleash.strategy;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import no.finn.unleash.UnleashContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FlexibleRolloutStrategyTest {
+
+    @Test
+    public void should_have_correct_name() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        assertEquals("flexibleRollout", strategy.getName());
+    }
+
+    @Test
+    public void should_always_be_false() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        assertFalse(strategy.isEnabled(new HashMap<>()));
+    }
+
+    @Test
+    public void should_NOT_be_enabled_for_rollout_9_and_userId_61() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "9");
+        params.put("stickiness", "default");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().userId("61").build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertFalse(enabled);
+    }
+
+    @Test
+    public void should_be_enabled_for_rollout_10_and_userId_61() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "default");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().userId("61").build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertTrue(enabled);
+    }
+
+    @Test
+    public void should_be_enabled_for_rollout_10_and_userId_61_and_stickiness_userId() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "userId");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().userId("61").build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertTrue(enabled);
+    }
+
+    @Test
+    public void should_be_disabled_when_userId_missing() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "100");
+        params.put("stickiness", "userId");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertFalse(enabled);
+    }
+
+    @Test
+    public void should_be_enabled_for_rollout_10_and_sessionId_61() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "default");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().sessionId("61").build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertTrue(enabled);
+    }
+    @Test
+    public void should_be_enabled_for_rollout_10_and_randomId_61_and_stickiness_sessionId() {
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy();
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "sessionId");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().sessionId("61").build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertTrue(enabled);
+    }
+
+    @Test
+    public void should_be_enabled_for_rollout_10_and_randomId_61_and_stickiness_default() {
+        Supplier<String> radnomGenerator = () -> "61";
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy(radnomGenerator);
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "default");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertTrue(enabled);
+    }
+
+    @Test
+    public void should_be_enabled_for_rollout_10_and_randomId_61_and_stickiness_random() {
+        Supplier<String> radnomGenerator = () -> "61";
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy(radnomGenerator);
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "random");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertTrue(enabled);
+    }
+
+
+
+    @Test
+    public void should_NOT_be_enabled_for_rollout_10_and_randomId_1() {
+        Supplier<String> radnomGenerator = () -> "1";
+        FlexibleRolloutStrategy strategy = new FlexibleRolloutStrategy(radnomGenerator);
+        Map<String, String> params = new HashMap<>();
+        params.put("rollout", "10");
+        params.put("stickiness", "default");
+        params.put("groupId", "Demo");
+
+        UnleashContext context = UnleashContext.builder().build();
+        boolean enabled = strategy.isEnabled(params, context);
+        assertFalse(enabled);
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/no/finn/unleash/strategy/StrategyTest.java b/src/test/java/no/finn/unleash/strategy/StrategyTest.java
new file mode 100644
index 00000000..8c9b4677
--- /dev/null
+++ b/src/test/java/no/finn/unleash/strategy/StrategyTest.java
@@ -0,0 +1,123 @@
+package no.finn.unleash.strategy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import no.finn.unleash.Constraint;
+import no.finn.unleash.Operator;
+import no.finn.unleash.UnleashContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class StrategyTest {
+
+    private class AlwaysEnabled implements Strategy {
+        @Override
+        public String getName() {
+            return "enabled";
+        }
+
+        @Override
+        public boolean isEnabled(Map<String, String> parameters) {
+            return true;
+        }
+    }
+
+    @Test
+    public void should_be_enabled_for_empty_constraints() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder().build();
+        List<Constraint> constraints = new ArrayList<>();
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertTrue(result);
+    }
+
+    @Test
+    public void should_be_enabled_for_null_constraints() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder().build();
+        List<Constraint> constraints = null;
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertTrue(result);
+    }
+
+    @Test
+    public void should_be_disabled_when_constraint_not_satisfied() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder().environment("test").build();
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.IN, Arrays.asList("prod")));
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertFalse(result);
+    }
+
+    @Test
+    public void should_be_enabled_when_constraint_is_satisfied() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder().environment("test").build();
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.IN, Arrays.asList("test", "prod")));
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertTrue(result);
+    }
+
+    @Test
+    public void should_be_enabled_when_constraint_NOT_IN_satisfied() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder().environment("test").build();
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.NOT_IN, Arrays.asList("prod")));
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertTrue(result);
+    }
+
+    @Test
+    public void should_be_enabled_when_all_constraints_are_satisfied() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder()
+                .environment("test")
+                .userId("123")
+                .addProperty("customerId", "blue")
+                .build();
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.IN, Arrays.asList("test", "prod")));
+        constraints.add(new Constraint("userId", Operator.IN, Arrays.asList("123")));
+        constraints.add(new Constraint("customerId", Operator.IN, Arrays.asList("red", "blue")));
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertTrue(result);
+    }
+
+    @Test
+    public void should_be_disabled_when_not_all_constraints_are_satisfied() {
+        Strategy s = new AlwaysEnabled();
+        Map parameters = new HashMap<String, String>();
+        UnleashContext context = UnleashContext.builder()
+                .environment("test")
+                .userId("123")
+                .addProperty("customerId", "orange")
+                .build();
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new Constraint("environment", Operator.IN, Arrays.asList("test", "prod")));
+        constraints.add(new Constraint("userId", Operator.IN, Arrays.asList("123")));
+        constraints.add(new Constraint("customerId", Operator.IN, Arrays.asList("red", "blue")));
+
+        boolean result = s.isEnabled(parameters, context, constraints);
+        assertFalse(result);
+    }
+}
\ No newline at end of file