diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy index ea0b6b4a9f..a98088b672 100755 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WiremockRequestStubStrategy.groovy @@ -1,5 +1,4 @@ package io.codearte.accurest.dsl - import groovy.transform.PackageScope import groovy.transform.TypeChecked import io.codearte.accurest.dsl.internal.Body @@ -13,11 +12,12 @@ import io.codearte.accurest.util.ContentType import java.util.regex.Pattern -import static io.codearte.accurest.util.ContentUtils.extractValue -import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHeader import static io.codearte.accurest.util.ContentUtils.getEqualsTypeFromContentType import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromContent +import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromHeader import static io.codearte.accurest.util.ContentUtils.recognizeContentTypeFromMatchingStrategy +import static io.codearte.accurest.util.RegexpBuilders.buildGStringRegexpMatch +import static io.codearte.accurest.util.RegexpBuilders.buildJSONRegexpMatch @TypeChecked @PackageScope @@ -93,58 +93,73 @@ class WiremockRequestStubStrategy extends BaseWiremockStubStrategy { return [appendBodyPattern(matchingStrategy)] } - private List> appendBodyPatterns(List matchingStrategies) { - return matchingStrategies.collect { appendBodyPattern(it) } - } - - private List> appendBodyPatterns(GString gString) { - if (containsPattern(gString)) { - Object value = extractValue(gString, { DslProperty dslProperty -> dslProperty.clientValue }) - return appendBodyPatterns(extractReqexpMatching(value)) - } - return appendBodyPatterns(new MatchingStrategy(gString, getEqualsTypeFromContentTypeHeader())) - } - private List> appendBodyPatterns(Object bodyValue) { - return appendBodyPatterns(new MatchingStrategy(bodyValue, MatchingStrategy.Type.EQUAL_TO)) + return appendBodyPatterns(new MatchingStrategy(bodyValue, getEqualsTypeFromContentTypeHeader())) } private Map appendBodyPattern(MatchingStrategy matchingStrategy) { MatchingStrategy.Type type = matchingStrategy.type - Object value= matchingStrategy.clientValue + Object value = matchingStrategy.clientValue ContentType contentType = recognizeContentTypeFromMatchingStrategy(type) if (contentType == ContentType.UNKNOWN && type == MatchingStrategy.Type.EQUAL_TO) { contentType = recognizeContentTypeFromContent(value) type = getEqualsTypeFromContentType(contentType) } - Map result = [(type.name): parseBody(value, contentType)] - if (type == MatchingStrategy.Type.EQUAL_TO_JSON && matchingStrategy.jsonCompareMode) { + if (containsPattern(value)) { + return appendBodyRegexpMatchPattern(value, contentType) + } + return buildMatchPattern(new MatchingStrategy(parseBody(value, contentType), type)) + } + + private Map appendBodyRegexpMatchPattern(Object value, ContentType contentType) { + switch (contentType) { + case ContentType.JSON: + return buildMatchPattern(new MatchingStrategy(buildJSONRegexpMatch(value), MatchingStrategy.Type.MATCHING)) + case ContentType.UNKNOWN: + return buildMatchPattern(new MatchingStrategy(buildGStringRegexpMatch(value), MatchingStrategy.Type.MATCHING)) + case ContentType.XML: + throw new IllegalStateException("XML pattern matching is not implemented yet") + } + } + + private Map buildMatchPattern(MatchingStrategy matchingStrategy) { + Map result = [(matchingStrategy.type.name): matchingStrategy.clientValue.toString()] + if (matchingStrategy.type == MatchingStrategy.Type.EQUAL_TO_JSON && matchingStrategy.jsonCompareMode) { return result << [jsonCompareMode : (matchingStrategy.jsonCompareMode.toString())] } return result } private boolean containsPattern(GString bodyAsValue) { - return bodyAsValue.values.collect { it instanceof DslProperty ? it.clientValue : it } - .find { it instanceof Pattern } + return containsPattern(bodyAsValue.values) } - private List extractReqexpMatching(Object responseBodyObject) { - def matchingStrategies = new ArrayList() - if (responseBodyObject instanceof GString) { - return [new MatchingStrategy(responseBodyObject, MatchingStrategy.Type.MATCHING)] - } else if (responseBodyObject instanceof Map) { - responseBodyObject.each { k, v -> - if (v instanceof List) { - v.each { - matchingStrategies.addAll(extractReqexpMatching((Map)it)) - } - } else { - matchingStrategies.add(new MatchingStrategy(/.*${k}":.?"?${v}"?.*/, MatchingStrategy.Type.MATCHING)) - } - } - } - return matchingStrategies + private boolean containsPattern(Map map) { + return containsPattern(map.entrySet()) + } + + private boolean containsPattern(Collection collection) { + return collection.collect(this.&containsPattern).inject { a, b -> a || b } + } + + private boolean containsPattern(Object[] objects) { + return containsPattern(objects.toList()) + } + + private boolean containsPattern(Map.Entry entry) { + return containsPattern(entry.value) + } + + private boolean containsPattern(DslProperty dslProperty) { + return containsPattern(dslProperty.clientValue) + } + + private boolean containsPattern(Pattern pattern) { + return true + } + + private boolean containsPattern(Object o) { + return false } private MatchingStrategy.Type getEqualsTypeFromContentTypeHeader() { diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/RegexpBuilders.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/RegexpBuilders.groovy new file mode 100644 index 0000000000..47cb595220 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/RegexpBuilders.groovy @@ -0,0 +1,72 @@ +package io.codearte.accurest.util + +import io.codearte.accurest.dsl.internal.DslProperty +import org.codehaus.groovy.runtime.GStringImpl + +import java.util.regex.Pattern + +import static io.codearte.accurest.util.ContentUtils.extractValue +import static org.apache.commons.lang3.StringEscapeUtils.escapeJson + +public class RegexpBuilders { + + public static String buildGStringRegexpMatch(GString gString) { + new GStringImpl( + gString.values.collect(this.&buildGStringRegexpMatch) as Object[], + gString.strings.collect(this.&escapeSpecialRegexChars) as String[] + ) + } + + public static String buildGStringRegexpMatch(Pattern pattern) { + return pattern.pattern() + } + + public static String buildGStringRegexpMatch(DslProperty dslProperty) { + return buildGStringRegexpMatch(dslProperty.clientValue) + } + + public static String buildGStringRegexpMatch(Object o) { + return escapeSpecialRegexChars(o.toString()) + } + + private final static Pattern SPECIAL_REGEX_CHARS = Pattern.compile('[{}()\\[\\].+*?^$\\\\|]') + + private static String escapeSpecialRegexChars(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll('\\\\$0') + } + + private final static String WS = /\s*/ + + public static String buildJSONRegexpMatch(GString gString) { + return buildJSONRegexpMatch(extractValue(gString, ContentType.JSON, { DslProperty dslProperty -> dslProperty.clientValue })) + } + + public static String buildJSONRegexpMatch(Map jsonMap) { + return WS + "\\{" + jsonMap.collect(this.&buildJSONRegexpMatch).join(",") + "\\}" + WS + } + + public static String buildJSONRegexpMatch(List jsonList) { + return WS + "\\[" + jsonList.collect(this.&buildJSONRegexpMatch).join(",") + "\\]" + WS + } + + public static String buildJSONRegexpMatch(Map.Entry entry) { + return buildJSONRegexpMatchString(escapeJson(entry.key)) + ":" + buildJSONRegexpMatch(entry.value) + } + + public static String buildJSONRegexpMatch(Object value) { + return buildJSONRegexpMatchStringOptionalQuotes(escapeJson(value.toString())) + } + + public static String buildJSONRegexpMatch(Pattern pattern) { + return buildJSONRegexpMatchStringOptionalQuotes(pattern.pattern()) + } + + public static String buildJSONRegexpMatchString(String value) { + return WS + '"' + value + '"' + WS + } + + public static String buildJSONRegexpMatchStringOptionalQuotes(String value) { + return WS + '"?' + value + '"?' + WS + } + +} diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy index 80540ec70d..946e72064c 100755 --- a/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/dsl/WiremockGroovyDslSpec.groovy @@ -553,7 +553,7 @@ class WiremockGroovyDslSpec extends WiremockSpec { "method": "GET", "urlPattern": "/[0-9]{2}", "bodyPatterns": [ - {"matches": ".*personalId\\":.?\\"?^[0-9]{11}$\\"?.*"} + {"matches": "\\\\s*\\\\{\\\\s*\\\"personalId\\\"\\\\s*:\\\\s*\\\"?^[0-9]{11}$\\\"?\\\\s*\\\\}\\\\s*"} ] }, "response": { @@ -613,8 +613,7 @@ class WiremockGroovyDslSpec extends WiremockSpec { }, "url": "/fraudcheck", "bodyPatterns": [ - {"matches": ".*clientPesel\\":.?\\"?[0-9]{10}\\"?.*"}, - {"matches": ".*loanAmount\\":.?\\"?123.123\\"?.*"} + {"matches": "\\\\s*\\\\{\\\\s*\\"clientPesel\\"\\\\s*:\\\\s*\\"?[0-9]{10}\\"?\\\\s*,\\\\s*\\"loanAmount\\"\\\\s*:\\\\s*\\"?123.123\\"?\\\\s*\\\\}\\\\s*"} ] }, "response": { @@ -986,17 +985,11 @@ class WiremockGroovyDslSpec extends WiremockSpec { "request": { "method": "GET", "urlPattern": "/[0-9]{2}", - "bodyPatterns": [ - {"matches": ".*birthDate\\":.?\\"?[0-9]{4}-[0-9]{2}-[0-9]{2}\\"?.*"}, - {"matches": ".*propertyName\\":.?\\"?[0-9]{2}\\"?.*"}, - {"matches": ".*providerValue\\":.?\\"?Test\\"?.*"}, - {"matches": ".*propertyName\\":.?\\"?[0-9]{2}\\"?.*"}, - {"matches": ".*providerValue\\":.?\\"?Test\\"?.*"}, - {"matches": ".*firstName\\":.?\\"?.*\\"?.*"}, - {"matches": ".*lastName\\":.?\\"?.*\\"?.*"}, - {"matches": ".*personalId\\":.?\\"?[0-9]{11}\\"?.*"} - ] - }, + "bodyPatterns": [ + { + "matches": "\\\\s*\\\\{\\\\s*\\"birthDate\\"\\\\s*:\\\\s*\\"?[0-9]{4}-[0-9]{2}-[0-9]{2}\\"?\\\\s*,\\\\s*\\"errors\\"\\\\s*:\\\\s*\\\\[\\\\s*\\\\{\\\\s*\\"propertyName\\"\\\\s*:\\\\s*\\"?[0-9]{2}\\"?\\\\s*,\\\\s*\\"providerValue\\"\\\\s*:\\\\s*\\"?Test\\"?\\\\s*\\\\}\\\\s*,\\\\s*\\\\{\\\\s*\\"propertyName\\"\\\\s*:\\\\s*\\"?[0-9]{2}\\"?\\\\s*,\\\\s*\\"providerValue\\"\\\\s*:\\\\s*\\"?Test\\"?\\\\s*\\\\}\\\\s*\\\\]\\\\s*,\\\\s*\\"firstName\\"\\\\s*:\\\\s*\\"?.*\\"?\\\\s*,\\\\s*\\"lastName\\"\\\\s*:\\\\s*\\"?.*\\"?\\\\s*,\\\\s*\\"personalId\\"\\\\s*:\\\\s*\\"?[0-9]{11}\\"?\\\\s*\\\\}\\\\s*" + } + ] }, "response": { "status": 200, "body": "{\\"name\\":\\"Jan\\"}", @@ -1008,6 +1001,58 @@ class WiremockGroovyDslSpec extends WiremockSpec { ''') } + def 'should use regexp matches when request body match is defined using a map with a pattern'() { + given: + GroovyDsl groovyDsl = GroovyDsl.make { + request { + method 'POST' + url '/reissue-payment-order' + body( + loanNumber: "999997001", + amount: value(client(regex('[0-9.]+')), server('100.00')), + currency: "DKK", + applicationName: value(client(regex('.*')), server("Auto-Repayments")), + username: value(client(regex('.*')), server("scheduler")), + cardId: 1 + ) + } + response { + status 200 + body ''' + { + "status": "OK" + } + ''' + headers { + header 'Content-Type': 'application/json' + } + } + } + when: + def json = toWiremockClientJsonStub(groovyDsl) + then: + parseJson(json) == parseJson(''' + { + "request": { + "method": "POST", + "url": "/reissue-payment-order", + "bodyPatterns": [ + { + "matches": "\\\\s*\\\\{\\\\s*\\"loanNumber\\"\\\\s*:\\\\s*\\"?999997001\\"?\\\\s*,\\\\s*\\"amount\\"\\\\s*:\\\\s*\\"?[0-9.]+\\"?\\\\s*,\\\\s*\\"currency\\"\\\\s*:\\\\s*\\"?DKK\\"?\\\\s*,\\\\s*\\"applicationName\\"\\\\s*:\\\\s*\\"?.*\\"?\\\\s*,\\\\s*\\"username\\"\\\\s*:\\\\s*\\"?.*\\"?\\\\s*,\\\\s*\\"cardId\\"\\\\s*:\\\\s*\\"?1\\"?\\\\s*\\\\}\\\\s*" + } + ] + }, + "response": { + "status": 200, + "body": "{\\"status\\":\\"OK\\"}", + "headers": { + "Content-Type": "application/json" + } + } + } + ''') + } + String toJsonString(value) { new JsonBuilder(value).toPrettyString() }