adding dynamic err message support for violations 23/69323/4
authorPierre Rioux <pierre.rioux@amdocs.com>
Thu, 27 Sep 2018 13:38:31 +0000 (09:38 -0400)
committerPierre Rioux <pierre.rioux@amdocs.com>
Wed, 3 Oct 2018 04:31:28 +0000 (00:31 -0400)
Change-Id: I80c1a0cade46ef623fce91921449642d8eafb2f6
Issue-ID: LOG-683
Signed-off-by: Pierre Rioux <pierre.rioux@amdocs.com>
.gitignore
bundleconfig/etc/rules/poa-event/default-rules.groovy [deleted file]
src/main/java/org/onap/aai/validation/config/TopicConfig.java
src/main/java/org/onap/aai/validation/ruledriven/RuleDrivenValidator.java
src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java
src/main/java/org/onap/aai/validation/ruledriven/rule/Rule.java
src/main/java/org/onap/aai/validation/ruledriven/rule/RuleResult.java [new file with mode: 0644]
src/test/java/org/onap/aai/validation/ruledriven/rule/RuleHelper.java
src/test/java/org/onap/aai/validation/ruledriven/rule/TestRuleExecution.java

index 59551a6..73b0166 100644 (file)
@@ -5,5 +5,6 @@ bundleconfig-local/etc/auth/tomcat_keystore
 .classpath
 .project
 .settings/
+staticContent/
 target/
 src/main/java/META-INF/
diff --git a/bundleconfig/etc/rules/poa-event/default-rules.groovy b/bundleconfig/etc/rules/poa-event/default-rules.groovy
deleted file mode 100644 (file)
index 8397abd..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * ============LICENSE_START===================================================
- * Copyright (c) 2018 Amdocs
- * ============================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=====================================================
- */
-
-entity {
-    name 'POA-EVENT'
-    indexing {
-        indices 'default-rules'
-    }
-       validation {
-               useRule {
-                       name 'Verify AAI nf-naming-code'
-                       attributes 'context-list.aai.vf-list[*]'
-               }
-               useRule {
-                       name 'port-mirroring-AAI-has-valid-vnfc'
-                       attributes 'context-list.sdc.vf-list[*]', 'context-list.aai.vf-list[*]'
-               }
-               useRule {
-                       name 'port-mirroring-SDC-vnfc-types-missing'
-                       attributes 'context-list.sdc.vf-list[*]', 'context-list.aai.vf-list[*]'
-               }
-               useRule {
-                       name 'port-mirroring-AAI-vnfc-type-exists-in-SDC-SUCCESS'
-                       attributes 'context-list.sdc.vf-list[*]', 'context-list.aai.vf-list[*]'
-               }
-       }
-}
-
-rule {
-       name        'Verify AAI nf-naming-code'
-       category    'INVALID_VALUE'
-       description 'Validate that nf-naming-code exists and is populated in AAI VNF instance'
-       errorText   'The nf-naming-code is not populated in AAI VNF instance'
-       severity    'CRITICAL'
-       attributes  'vfList'
-       validate    '''
-                               def parsed = new groovy.json.JsonSlurper().parseText(vfList.toString())
-                               for (vf in parsed) {
-                                       String nfNamingCode = vf."nf-naming-code"
-                                       if (nfNamingCode == null || nfNamingCode.equals("")) {
-                                               return false
-                                       }
-                               }
-                               return true
-                '''
-}
-
-rule {
-       name        'port-mirroring-AAI-has-valid-vnfc'
-       category    'INVALID_VALUE'
-       description 'Validate that each VNFC instance in AAI conforms to a VNFC type defined in SDC model'
-       errorText   'AAI VNFC instance includes non-specified type in design SDC model'
-       severity    'ERROR'
-       attributes  'sdcVfList', 'aaiVfList'
-       validate    '''
-                               def slurper = new groovy.json.JsonSlurper()
-                               def parsedSdc = slurper.parseText(sdcVfList.toString())
-                               def parsedAai = slurper.parseText(aaiVfList.toString())
-
-                               // gather all SDC nfc-naming-codes
-                               List<String> sdcNfcNamingCodeList = new ArrayList<>()
-                               parsedSdc.each {
-                                       for(sdcVnfc in it.vnfc) {
-                                               String sdcNfcNamingCode = sdcVnfc."nfc-naming-code"
-                                               if(sdcNfcNamingCode != null) {
-                                                       sdcNfcNamingCodeList.add(sdcNfcNamingCode)
-                                               }
-                                       }
-                               }
-
-                               // check that all SDC nfc-naming-codes exist in AAI
-                               parsedAai.each {
-                                       for(aaiVnfc in it.vnfc) {
-                                               String aaiNfcNamingCode = aaiVnfc."nfc-naming-code"
-                                               if(aaiNfcNamingCode != null) {
-                                                       if(!sdcNfcNamingCodeList.contains(aaiNfcNamingCode)) {
-                                                               return false
-                                                       }
-                                               }
-                                       }
-                               }
-                               return true
-                '''
-}
-
-
-rule {
-       name        'port-mirroring-SDC-vnfc-types-missing'
-       category    'INVALID_VALUE'
-       description 'Validate that each VNFC type specified in SDC model exists in AAI'
-       errorText   'Design has specified types but not all of them exist in AAI'
-       severity    'WARNING'
-       attributes  'sdcVfList', 'aaiVfList'
-       validate    '''
-                               def getNfcNamingCodeSet = { parsedEntity ->
-                                       Set<String> namingCodeSet = new HashSet<>()
-                                       parsedEntity.each {
-                                               for(vnfcItem in it."vnfc") {
-                                                       println "vnfc: " + vnfcItem
-                                                       String namingCode = vnfcItem."nfc-naming-code"
-                                                       if(namingCode != null) {
-                                                               namingCodeSet.add(namingCode)
-                                                       }
-                                               }
-                                       }
-                                       return namingCodeSet
-                               }
-
-                               // gather all unique nfc-naming-codes from AAI and SDC
-                               def slurper = new groovy.json.JsonSlurper()
-                               def aaiNfcNamingCodeSet = getNfcNamingCodeSet(slurper.parseText(aaiVfList.toString())) as java.util.HashSet
-                               def sdcNfcNamingCodeSet = getNfcNamingCodeSet(slurper.parseText(sdcVfList.toString())) as java.util.HashSet
-                               
-                               println "AAI: " + aaiNfcNamingCodeSet
-                               println "SDC: " + sdcNfcNamingCodeSet
-
-                               // check that all nfc-naming-codes in SDC exist in AAI
-                               return aaiNfcNamingCodeSet.containsAll(sdcNfcNamingCodeSet)
-                '''
-}
-
-
-rule {
-       name        'port-mirroring-AAI-vnfc-type-exists-in-SDC-SUCCESS'
-       category    'SUCCESS'
-       description 'Verify that every vnfc in sdc has been created in AAI'
-       errorText   'Every vnfc type specified in sdc has been created in AAI'
-       severity    'INFO'
-       attributes  'sdcVfList', 'aaiVfList'
-       validate    '''
-                               def getNfcNamingCodeSet = { parsedEntity ->
-                                       Set<String> namingCodeSet = new HashSet<>()
-                                       parsedEntity.each {
-                                               for(vnfcItem in it."vnfc") {
-                                                       String namingCode = vnfcItem."nfc-naming-code"
-                                                       if(namingCode != null) {
-                                                               namingCodeSet.add(namingCode)
-                                                       }
-                                               }
-                                       }
-                                       return namingCodeSet
-                               }
-
-                               // gather all unique nfc-naming-codes from AAI and SDC
-                               def slurper = new groovy.json.JsonSlurper()
-                               def aaiNfcNamingCodeSet = getNfcNamingCodeSet(slurper.parseText(aaiVfList.toString())) as java.util.HashSet
-                               def sdcNfcNamingCodeSet = getNfcNamingCodeSet(slurper.parseText(sdcVfList.toString())) as java.util.HashSet
-
-                               // check that all nfc-naming-codes in SDC exist in AAI
-                               // return false if all SDC naming codes exist in AAI to trigger an INFO violation
-                               return !aaiNfcNamingCodeSet.containsAll(sdcNfcNamingCodeSet)
-                               '''
-}
index 99742ac..ccf5d51 100644 (file)
@@ -46,13 +46,12 @@ public class TopicConfig {
     List<Topic> publisherTopics = new ArrayList<>();
 
     @Autowired
-    public TopicConfig (@Value("${consumer.topic.names}") final String consumerNames, @Value("${publisher.topic.names}") final String publisherNames){
-
-        consumerTopicNames = Arrays.asList(consumerNames.split(","));;
-        publisherTopicNames = Arrays.asList(publisherNames.split(","));;
-
-
+    public TopicConfig (@Value("${consumer.topic.names}") final String consumerNames,
+            @Value("${publisher.topic.names}") final String publisherNames) {
+        consumerTopicNames = Arrays.asList(consumerNames.split(","));
+        publisherTopicNames = Arrays.asList(publisherNames.split(","));
     }
+
     /**
      * Gets the configuration of topics for consumption.
      *
index 476c098..2bc2d90 100644 (file)
@@ -21,6 +21,7 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -49,6 +50,7 @@ import org.onap.aai.validation.ruledriven.configuration.EntitySection;
 import org.onap.aai.validation.ruledriven.configuration.GroovyConfigurationException;
 import org.onap.aai.validation.ruledriven.configuration.RulesConfigurationLoader;
 import org.onap.aai.validation.ruledriven.rule.Rule;
+import org.onap.aai.validation.ruledriven.rule.RuleResult;
 
 /**
  * Validator using explicit rules
@@ -159,28 +161,30 @@ public class RuleDrivenValidator implements Validator {
             AttributeValues attributeValues = entity.getAttributeValues(rule.getAttributePaths());
 
             // Execute the rule for this particular set of attribute values.
-            boolean valid = false;
+            RuleResult result = null;
             try {
-                valid = rule.execute(attributeValues);
+                result = rule.execute(attributeValues);
             } catch (IllegalArgumentException e) {
                 throw new ValidationServiceException(ValidationServiceError.RULE_EXECUTION_ERROR, e, rule,
                         attributeValues);
             }
 
             applicationLogger.debug(String.format("%s|%s|\"%s\"|%s", entity.getType(), entity.getIds(), rule.getName(),
-                    valid ? "pass" : "fail"));
+                    result.getSuccess() ? "pass" : "fail"));
+
+            if (!result.getSuccess()) {
+                String errorMessage = MessageFormat.format(rule.getErrorMessage(), result.getErrorArguments().toArray());
 
-            if (!valid) {
                 //@formatter:off
-                               Violation violation = builder
-                                                                       .category(rule.getErrorCategory())
-                                                                       .severity(rule.getSeverity())
-                                                                       .violationType(ViolationType.RULE)
-                                                                       .validationRule(rule.getName())
-                                                                       .violationDetails(attributeValues.generateReport())
-                                                                       .errorMessage(rule.getErrorMessage())
-                                                                       .build();
-                               //@formatter:on
+                Violation violation = builder
+                        .category(rule.getErrorCategory())
+                        .severity(rule.getSeverity())
+                        .violationType(ViolationType.RULE)
+                        .validationRule(rule.getName())
+                        .violationDetails(attributeValues.generateReport())
+                        .errorMessage(errorMessage)
+                        .build();
+                //@formatter:on
 
                 validationResult.addViolation(violation);
             }
index b151f6b..df15791 100644 (file)
@@ -125,7 +125,7 @@ public class GroovyRule implements Rule {
      * @return
      */
     @Override
-    public Boolean execute(AttributeValues attributeValues) {
+    public RuleResult execute(AttributeValues attributeValues) {
         // Obtain the values of each of the attributes to pass into the rule
         List<Object> valueList = new ArrayList<>();
         for (String attrName : this.attributePaths) {
@@ -141,10 +141,10 @@ public class GroovyRule implements Rule {
      * @param values
      *
      * @param groovyObject an instance/object of a Groovy class that implements one or more rule methods
-     * @return the Boolean result of evaluating the expression
+     * @return the result of evaluating the expression
      */
     @Override
-    public Boolean execute(Object... values) {
+    public RuleResult execute(Object... values) {
         Object result = null;
         try {
             result = groovyObject.invokeMethod(getRuleMethod(), values);
@@ -153,12 +153,7 @@ public class GroovyRule implements Rule {
         } catch (NullPointerException e) {
             throw new IllegalArgumentException("Argument is null", e);
         }
-
-        if (result instanceof Number) {
-            return !result.equals(0);
-        } else {
-            return (Boolean) result;
-        }
+        return new RuleResult(result);
     }
 
     @Override
index 1196db0..60705ea 100644 (file)
@@ -25,57 +25,57 @@ import org.onap.aai.validation.reader.data.AttributeValues;
  */
 public interface Rule {
 
-       /**
-        * Gets the name of the rule
-        *
-        * @return the name
-        */
-       String getName();
+    /**
+     * Gets the name of the rule
+     *
+     * @return the name
+     */
+    String getName();
 
-       /**
-        * Gets the error message.
-        *
-        * @return the error message
-        */
-       String getErrorMessage();
+    /**
+     * Gets the error message.
+     *
+     * @return the error message
+     */
+    String getErrorMessage();
 
-       /**
-        * Gets the error category.
-        *
-        * @return the error category
-        */
-       String getErrorCategory();
+    /**
+     * Gets the error category.
+     *
+     * @return the error category
+     */
+    String getErrorCategory();
 
-       /**
-        * Gets the severity.
-        *
-        * @return the severity
-        */
-       String getSeverity();
+    /**
+     * Gets the severity.
+     *
+     * @return the severity
+     */
+    String getSeverity();
 
-       /**
-        * Gets the paths to the attributes to pass to the rule
-        *
-        * @return the attribute paths
-        */
-       List<String> getAttributePaths();
+    /**
+     * Gets the paths to the attributes to pass to the rule
+     *
+     * @return the attribute paths
+     */
+    List<String> getAttributePaths();
 
-       /**
-        * Execute the rule.
-        *
-        * @param values
-        *            the attribute values to pass to the rule
-        * @return a boolean representing the rule evaluation (meaning success/failure)
-        */
-       Boolean execute(AttributeValues values);
+    /**
+     * Execute the rule.
+     *
+     * @param values
+     *            the attribute values to pass to the rule
+     * @return a RuleResult instance representing the rule evaluation (meaning success/failure)
+     */
+    RuleResult execute(AttributeValues values);
 
-       /**
-        * Execute the rule.
-        *
-        * @param values
-        *            the attribute values to pass to the rule
-        * @return a boolean representing the rule evaluation (meaning success/failure)
-        */
-       Boolean execute(Object... values);
+    /**
+     * Execute the rule.
+     *
+     * @param values
+     *            the attribute values to pass to the rule
+     * @return a RuleResult instance representing the rule evaluation (meaning success/failure)
+     */
+    RuleResult execute(Object... values);
 
 }
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/rule/RuleResult.java b/src/main/java/org/onap/aai/validation/ruledriven/rule/RuleResult.java
new file mode 100644 (file)
index 0000000..19f77f7
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * ============LICENSE_START===================================================
+ * Copyright (c) 2018 Amdocs
+ * ============================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================================
+ */
+package org.onap.aai.validation.ruledriven.rule;
+
+import groovy.lang.Tuple2;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Encapsulates the results of rule execution
+ *
+ */
+public class RuleResult {
+
+    private Boolean success = true;
+    private List<String> errorArguments = Collections.emptyList();
+
+    /**
+     * Creates an instance of this class using the groovy object returned by rule execution.
+     *
+     * Valid object types:
+     *    Boolean: true = success; false = fail
+     *    Number: 0 = success; non-zero = fail
+     *    Tuple2: contains rule result and argument list
+     *       - tuple's "first" contains a boolean representing the results of rule execution
+     *       - tuple's "second" contains a list of strings used to expand rule error text
+     *
+     * @param groovyResult
+     */
+    public RuleResult(Object groovyResult) {
+        if (groovyResult instanceof Number) {
+            success = !((Number)groovyResult).equals(0);
+        } else if (groovyResult instanceof Tuple2) {
+            handleTuple(groovyResult);
+        } else {
+            success = (Boolean)groovyResult;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private RuleResult() {
+        // intentionally empty
+    }
+
+    /**
+     * Returns the results of rule execution, i.e. success or fail
+     * @return
+     */
+    public Boolean getSuccess() {
+        return success;
+    }
+
+    /**
+     * Returns the list of arguments used to expand rule error text.
+     *
+     * For example, this errorText in a rule definition:
+     *    'Error found with "{0}" in "{1}"; value "{2}" is not a valid MAC address'
+     *
+     * used with the following runtime argument list:
+     *    ["macaddr", "tenants.tenant.vservers.vserver.l-interfaces.l-interface", "02:fd:59:3"]
+     *
+     * would display:
+     *    Error found with "macaddr" in "tenants.tenant.vservers.vserver.l-interfaces.l-interface"; value "02:fd:59:3" is not a valid MAC address
+     *
+     * @return a list of strings; will not return null
+     */
+    public List<String> getErrorArguments() {
+        return errorArguments;
+    }
+
+    /**
+     * Handles a Tuple2 object returned by a groovy rule.
+     * The tuple's "first" contains a boolean representing the results of rule execution.
+     * The tuple's "second" contains a list of strings used to expand rule error text.
+     * @param tupleObject
+     */
+    private void handleTuple(Object tupleObject) {
+        @SuppressWarnings("unchecked")
+        Tuple2<Boolean, List<String>> tuple = (Tuple2<Boolean, List<String>>)tupleObject;
+        success = tuple.getFirst();
+        errorArguments = (tuple.getSecond() == null) ? Collections.emptyList() : tuple.getSecond();
+    }
+}
index 456d011..c9d6284 100644 (file)
@@ -21,16 +21,23 @@ import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
+import java.text.MessageFormat;
 import org.onap.aai.validation.reader.data.AttributeValues;
-import org.onap.aai.validation.ruledriven.rule.Rule;
 
 public class RuleHelper {
 
     static void assertRuleResult(Rule rule, AttributeValues values, Boolean expectedResult) {
-        assertThat(rule + " failed for values [" + values + "]", rule.execute(values), is(equalTo(expectedResult)));
+        assertThat(rule + " failed for values [" + values + "]", rule.execute(values).getSuccess(), is(equalTo(expectedResult)));
     }
 
     static void assertRuleResult(Rule rule, Object value, Boolean expectedResult) {
-        assertThat(rule + " failed for value [" + value + "]", rule.execute(value), is(equalTo(expectedResult)));
+        assertThat(rule + " failed for value [" + value + "]", rule.execute(value).getSuccess(), is(equalTo(expectedResult)));
+    }
+
+    static void assertRuleErrorMessage(Rule rule, Object value, String expectedErrorMessage) {
+        RuleResult result = rule.execute(value);
+        String errorMessage = MessageFormat.format(rule.getErrorMessage(), result.getErrorArguments().toArray());
+        assertThat(rule + " failed to validate error message [" + expectedErrorMessage + "]",
+                errorMessage, is(equalTo(expectedErrorMessage)));
     }
 }
index 15e958d..426bbb4 100644 (file)
@@ -34,7 +34,6 @@ import org.junit.rules.ExpectedException;
 import org.onap.aai.validation.reader.data.AttributeValues;
 import org.onap.aai.validation.ruledriven.configuration.GroovyConfigurationException;
 import org.onap.aai.validation.ruledriven.configuration.RuleSection;
-import org.onap.aai.validation.ruledriven.rule.GroovyRule;
 
 /**
  * Tests for creating an AuditRule object and then executing the rule expression against fixed attribute values
@@ -81,6 +80,51 @@ public class TestRuleExecution {
         assertRuleResult(rule, Arrays.asList(5, 44), false);
     }
 
+    /**
+     * Simple example of a rule using error message expansion
+     * @throws Exception
+     */
+    @Test
+    public void testRuleErrorTextWithArguments() throws Exception {
+
+        final String errorMessage = "Error message with arguments: {0}, {1}";
+        final String expectedErrorMessage = "Error message with arguments: arg1, arg2";
+
+        String expression = "return new groovy.lang.Tuple2(true, java.util.Arrays.asList(\"arg1\", \"arg2\"))";
+        GroovyRule rule = buildRuleWithErrorMessage("i", expression, errorMessage);
+        assertRuleResult(rule, 1, expectedErrorMessage);
+
+        String expressionOneArgumentTooMany = "return new groovy.lang.Tuple2(true, java.util.Arrays.asList(\"arg1\", \"arg2\", \"arg3\"))";
+        GroovyRule rule3 = buildRuleWithErrorMessage("i", expressionOneArgumentTooMany, errorMessage);
+        assertRuleResult(rule3, 1, expectedErrorMessage);
+
+        String expressionOneLessArgument = "return new groovy.lang.Tuple2(true, java.util.Arrays.asList(\"arg1\"))";
+        GroovyRule rule2 = buildRuleWithErrorMessage("i", expressionOneLessArgument, errorMessage);
+        assertRuleResult(rule2, 1, "Error message with arguments: arg1, {1}");
+    }
+
+    /**
+     * Simple example of a rule using error message expansion, without arguments
+     * @throws Exception
+     */
+    @Test
+    public void testRuleErrorTextWithoutArguments() throws Exception {
+
+        final String errorMessage = "Error message without arguments";
+
+        String expressionWithArgs = "return new groovy.lang.Tuple2(true, java.util.Arrays.asList(\"arg1\", \"arg2\"))";
+        GroovyRule rule = buildRuleWithErrorMessage("i", expressionWithArgs, errorMessage);
+        assertRuleResult(rule, 1, errorMessage);
+
+        String expressionWithoutArgs = "return new groovy.lang.Tuple2(true, java.util.Collections.emptyList())";
+        GroovyRule rule2 = buildRuleWithErrorMessage("i", expressionWithoutArgs, errorMessage);
+        assertRuleResult(rule2, 1, errorMessage);
+
+        String expressionWithNullAsArgs = "return new groovy.lang.Tuple2(true, null)";
+        GroovyRule rule3 = buildRuleWithErrorMessage("i", expressionWithNullAsArgs, errorMessage);
+        assertRuleResult(rule3, 1, errorMessage);
+    }
+
     /**
      * vserver is related to vpe and vserver-name contains me6
      */
@@ -468,6 +512,18 @@ public class TestRuleExecution {
         return new GroovyRule(ruleConfig);
     }
 
+    private GroovyRule buildRuleWithErrorMessage(String name, String attribute, String expression, String errorMessage)
+            throws IOException, InstantiationException, IllegalAccessException, GroovyConfigurationException {
+        RuleSection ruleConfig = new RuleSection();
+        ruleConfig.setName(name);
+        ruleConfig.setAttributes(Collections.singletonList(attribute));
+        ruleConfig.setExpression(expression);
+        if(errorMessage != null) {
+            ruleConfig.setErrorMessage(errorMessage);
+        }
+        return new GroovyRule(ruleConfig);
+    }
+
     /**
      * Build a simple rule (with a default name) using a RuleConfiguration object
      *
@@ -487,8 +543,17 @@ public class TestRuleExecution {
         return buildRule("testRule", attributes, expression);
     }
 
+    private GroovyRule buildRuleWithErrorMessage(String attribute, String expression, String errorText)
+            throws InstantiationException, IllegalAccessException, IOException, GroovyConfigurationException {
+        return buildRuleWithErrorMessage("testRule", attribute, expression, errorText);
+    }
+
     private void assertRuleResult(GroovyRule rule, Object value, boolean expectedResult) {
         RuleHelper.assertRuleResult(rule, value, expectedResult);
     }
 
+    private void assertRuleResult(GroovyRule rule, Object value, String expectedErrorMessage) {
+        RuleHelper.assertRuleErrorMessage(rule, value, expectedErrorMessage);
+    }
+
 }