Skip to content

Commit

Permalink
fix(#341): Allow comma and brackets in matcher expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
schlathoeltt committed Sep 8, 2023
1 parent d4cf427 commit c41fe6f
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 6 deletions.
11 changes: 11 additions & 0 deletions src/manual/validation-hamcrest.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ receive("someEndpoint")
.expression("node-set:/TestRequest/OrderType", hasSize(3));
----

NOTE: If you want to match text containing any of the following characters: ' , ( )
You need to enclose the respective string in quotation marks when defining your matcher.
If you intend to match an actual single quote, it should be escaped with a backslash (\').

For example:
[source,xml]
----
anyOf(equalTo('text containing a \\' (quote) and a , (comma) '), anyOf(isEmptyOrNullString()))
anyOf(equalTo('text containing a backslash and quote \\\\' and a , (comma) '), anyOf(isEmptyOrNullString()))
----

.XML
[source,xml,indent=0,role="secondary"]
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private Matcher<?> getMatcher(String matcherName, String[] matcherParameter, Tes
}

if (matcherMethod != null) {
return (Matcher<?>) matcherMethod.invoke(null, matcherParameter[0]);
return (Matcher<?>) matcherMethod.invoke(null, matcherParameter[0].replace("\\'","'"));
}
}

Expand Down Expand Up @@ -457,7 +457,22 @@ public String toString() {
*/
private static class Tokenizer {

private static final Pattern TEXT_PARAMETER_PATTERN = Pattern.compile("\\(('[^']*')\\)|('[^']*')");
/**
* Regular expression with three alternative parts (ORed) to match:
* <ol>
* <li> `(sometext)` - Quoted parameter block of a matcher.</li>
* <li> 'sometext' - Quoted text used as a parameter to a string matcher.</li>
* <li> (unquotedtext) - Unquoted text used as a parameter to a string matcher. This expression is non-greedy, meaning the first closing bracket will terminate the match.</li>
* </ol>
* <p/>
* Please note:
* - 'sometext' may contain an escaped quote.
* - 'unquotedtext' must not contain brackets or commas.
* <p/>
* To match quotes, commas, or brackets, you must quote the text. To match a quote, it should be escaped with a backslash.
*/
private static final Pattern TEXT_PARAMETER_PATTERN = Pattern.compile("\\('(?<quoted1>((?:[^']|\\\\')*))[^\\\\]'\\)|('(?<quoted2>((?:[^']|\\\\')*))[^\\\\]')|\\((?<unquoted>(([^']|\\\\')*?)[^\\\\])\\)");


private final List<String> originalTokenValues = new ArrayList<>();

Expand All @@ -468,10 +483,13 @@ private static class Tokenizer {
* @return the expression with all relevant subexpressions replaced by tokens
*/
public String tokenize(String rawExpression) {

String tokenizedExpression = rawExpression;
java.util.regex.Matcher matcher = TEXT_PARAMETER_PATTERN.matcher(rawExpression);
while (matcher.find()) {
String param = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
String param = matcher.group("quoted1");
param = param != null ? param : matcher.group("quoted2");
param = param != null ? param : matcher.group("unquoted");
originalTokenValues.add(param);
tokenizedExpression = tokenizedExpression.replace(param, "$$" + originalTokenValues.size() + "$$");
}
Expand All @@ -487,7 +505,6 @@ public String tokenize(String rawExpression) {
*/
public String[] restoreInto(String[] expressions) {

// Replace tokens with real text.
for (int i = 0; i < expressions.length; i++) {
expressions[i] = VariableUtils.cutOffSingleQuotes(
replaceTokens(expressions[i], originalTokenValues).trim());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,17 @@ public Object[][] testData() {
new Object[]{"foo", "text containing a , (comma) ", Collections.singletonList("anyOf(equalTo('text containing a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "", Collections.singletonList("anyOf(equalTo('text containing a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", null, Collections.singletonList("anyOf(equalTo('text containing a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "INSERT INTO todo_entries (id, title, description, done) values (1, 'Invite for meeting', 'Invite the group for a lunch meeting', 'false')", Collections.singletonList("allOf(startsWith('INSERT INTO todo_entries (id, title, description, done)'))")},
new Object[]{"foo", "text-equalTo(QA, Max", Collections.singletonList("anyOf(equalTo('text-equalTo(QA, Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "", Collections.singletonList("anyOf(equalTo('text-equalTo(QA, Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", null, Collections.singletonList("anyOf(equalTo('text-equalTo(QA, Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "QA-equalTo(HH), Max", Collections.singletonList("anyOf(equalTo('QA-equalTo(HH), Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "text containing a ' (quote) and a , (comma) ", Collections.singletonList("anyOf(equalTo('text containing a \\' (quote) and a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "text containing a \\' (quote) and a , (comma) ", Collections.singletonList("anyOf(equalTo('text containing a \\\\' (quote) and a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "unquoted text may not include brackets or commas", Collections.singletonList("anyOf(equalTo(unquoted text may not include brackets or commas), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "unquoted \\' text may not include brackets or commas", Collections.singletonList("anyOf(equalTo(unquoted \\\\' text may not include brackets or commas), anyOf(isEmptyOrNullString()))")},

new Object[]{"foo", "INSERT INTO todo_entries (id, title, description, done) values (1, 'Invite for meeting', 'Invite the group for a lunch meeting', 'false')",
Collections.singletonList("allOf(startsWith('INSERT INTO todo_entries (id, title, description, done)'))")},


};
Expand Down Expand Up @@ -196,7 +206,14 @@ public Object[][] testDataFailed() {
new Object[]{"foo", "[value1,value2]", Collections.singletonList("hasItem(value5)") },
new Object[]{"foo", "[value1,value2]", Collections.singletonList("hasItems(value1,value2,value5)") },
new Object[]{"foo", "[value1,value2]", Collections.singletonList("contains(value1)") },
new Object[]{"foo", "[value1,value2]", Collections.singletonList("containsInAnyOrder(value2,value4)") }
new Object[]{"foo", "[value1,value2]", Collections.singletonList("containsInAnyOrder(value2,value4)") },
new Object[]{"foo", "notext-equalTo(QA, Max", Collections.singletonList("anyOf(equalTo('text-equalTo(QA, Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "aa", Collections.singletonList("anyOf(equalTo('text-equalTo(QA, Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "VA-equalTo(HH), Max", Collections.singletonList("anyOf(equalTo('QA-equalTo(HH), Max'),anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "notext containing a ' (quote) and a , (comma) ", Collections.singletonList("anyOf(equalTo('text containing a \\' (quote) and a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "notext containing a \\' (quote) and a , (comma) ", Collections.singletonList("anyOf(equalTo('text containing a \\\\' (quote) and a , (comma) '), anyOf(isEmptyOrNullString()))")},
new Object[]{"foo", "nounquoted text may not include brackets or commas", Collections.singletonList("anyOf(equalTo(unquoted text may not include brackets or commas), anyOf(isEmptyOrNullString()))")},

};
}
}

0 comments on commit c41fe6f

Please sign in to comment.