Allows passing of regex type (#832)

without this change if one does $(regex("[0-9]")) we have no knowledge of whether the result should be text or a number. What we do ATM is we always generate a String
with this change once can pass the type of regular expression and we will generate the concrete value of that given type

fixes gh-768
This commit is contained in:
Marcin Grzejszczak
2018-12-28 16:29:32 +01:00
committed by GitHub
parent 58bf533462
commit 56fbc11f62
40 changed files with 989 additions and 263 deletions

View File

@@ -30,6 +30,7 @@ import org.springframework.cloud.contract.spec.internal.Header
import org.springframework.cloud.contract.spec.internal.Input
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.NotToEscapePattern
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.util.MapConverter
@@ -264,6 +265,10 @@ class JUnitMessagingMethodBodyBuilder extends MessagingMethodBodyBuilder {
return ""
}
protected String convertHeaderComparison(RegexProperty headerValue) {
return convertHeaderComparison(headerValue.pattern)
}
protected String createHeaderComparison(Object headerValue) {
String escapedHeader = convertUnicodeEscapesIfRequired("$headerValue")
return "isEqualTo(\"$escapedHeader\");"

View File

@@ -41,6 +41,7 @@ import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.spec.internal.QueryParameter
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor
import org.springframework.cloud.contract.verifier.template.TemplateProcessor
@@ -197,6 +198,13 @@ abstract class MethodBodyBuilder {
*/
protected abstract void processHeaderElement(BlockBuilder blockBuilder, String property, Pattern pattern)
/**
* Appends to the {@link BlockBuilder} the assertion for the given header path
*/
protected void processHeaderElement(BlockBuilder blockBuilder, String property, RegexProperty regexProperty) {
processHeaderElement(blockBuilder, property, regexProperty.pattern)
}
/**
* Appends to the {@link BlockBuilder} the assertion for the given header path
*/
@@ -522,7 +530,7 @@ abstract class MethodBodyBuilder {
protected void methodForEqualityCheck(BodyMatcher bodyMatcher, BlockBuilder bb, Object copiedBody) {
String path = quotedAndEscaped(bodyMatcher.path())
Object retrievedValue = value(copiedBody, bodyMatcher)
retrievedValue = retrievedValue instanceof Pattern ? ((Pattern) retrievedValue).pattern() : retrievedValue
retrievedValue = retrievedValue instanceof RegexProperty ? ((RegexProperty) retrievedValue).getPattern().pattern() : retrievedValue
String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString()
if (arrayRelated(path) && MatchingType.regexRelated(bodyMatcher.matchingType())) {
buildCustomMatchingConditionForEachElement(bb, path, valueAsParam)

View File

@@ -27,6 +27,7 @@ import org.springframework.cloud.contract.spec.internal.Header
import org.springframework.cloud.contract.spec.internal.Input
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.NotToEscapePattern
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.util.MapConverter
@@ -251,6 +252,10 @@ class SpockMessagingMethodBodyBuilder extends MessagingMethodBodyBuilder {
return " == '$headerValue'"
}
protected String convertHeaderComparison(RegexProperty headerValue) {
return convertHeaderComparison(headerValue.pattern)
}
protected String convertHeaderComparison(Pattern headerValue) {
String converted = escapeJava(convertUnicodeEscapesIfRequired(headerValue.pattern()))
return "==~ java.util.regex.Pattern.compile('${converted}')"

View File

@@ -24,6 +24,7 @@ import org.springframework.cloud.contract.spec.internal.Cookie
import org.springframework.cloud.contract.spec.internal.FromFileProperty
import org.springframework.cloud.contract.spec.internal.Header
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
@@ -187,10 +188,18 @@ abstract class SpockMethodRequestProcessingBodyBuilder extends RequestProcessing
return patternComparison(headerValue)
}
protected String convertHeaderComparison(RegexProperty headerValue) {
return convertHeaderComparison(headerValue.pattern)
}
protected String convertCookieComparison(String cookieValue) {
return "== '$cookieValue'"
}
protected String createBodyComparison(RegexProperty bodyValue) {
return createBodyComparison(bodyValue.pattern)
}
protected String createBodyComparison(Pattern bodyValue) {
String patternAsString = bodyValue.pattern()
return patternComparison(RegexpBuilders.buildGStringRegexpForTestSide(patternAsString)) + ";"

View File

@@ -16,6 +16,8 @@ import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.Multipart
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.NotToEscapePattern
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.converter.YamlContract.RegexType
import org.springframework.cloud.contract.verifier.util.JsonPaths
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
import org.springframework.cloud.contract.verifier.util.MapConverter
@@ -141,12 +143,12 @@ class ContractsToYaml {
)
}
Object url = contract.request.url?.clientValue
request.matchers.url = url instanceof Pattern ?
request.matchers.url = url instanceof RegexProperty ?
new YamlContract.KeyValueMatcher(regex: url.pattern()) :
url instanceof ExecutionProperty ?
new YamlContract.KeyValueMatcher(command: url.toString()) : null
Object urlPath = contract.request.urlPath?.clientValue
request.matchers.url = urlPath instanceof Pattern ?
request.matchers.url = urlPath instanceof RegexProperty ?
new YamlContract.KeyValueMatcher(regex: urlPath.pattern()) :
urlPath instanceof ExecutionProperty ?
new YamlContract.KeyValueMatcher(command: urlPath.toString()) : null
@@ -158,9 +160,9 @@ class ContractsToYaml {
Object fileName = value.name?.clientValue
Object fileContent = value.value?.clientValue
Object contentType = value.contentType?.clientValue
if (fileName instanceof Pattern ||
fileContent instanceof Pattern ||
contentType instanceof Pattern) {
if (fileName instanceof RegexProperty ||
fileContent instanceof RegexProperty ||
contentType instanceof RegexProperty) {
request.matchers.multipart.named << new YamlContract.MultipartNamedStubMatcher(
paramName: key,
fileName: valueMatcher(fileName),
@@ -168,10 +170,12 @@ class ContractsToYaml {
contentType: valueMatcher(contentType),
)
}
} else if (value instanceof Pattern) {
} else if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value)
request.matchers.multipart.params.add(new YamlContract.KeyValueMatcher(
key: key,
regex: value.pattern()
regex: property.pattern(),
regexType: regexType(property.clazz())
))
}
}
@@ -191,27 +195,56 @@ class ContractsToYaml {
}
protected YamlContract.ValueMatcher valueMatcher(Object o) {
return o instanceof Pattern ? new YamlContract.ValueMatcher(regex: o.pattern()) : null
return o instanceof RegexProperty ? new YamlContract.ValueMatcher(regex: o.pattern()) : null
}
protected void setInputBodyMatchers(DslProperty body, List<YamlContract.BodyStubMatcher> bodyMatchers) {
def testSideValues = MapConverter.getTestSideValues(body)
JsonPaths paths = new JsonToJsonPathsConverter().transformToJsonPathWithStubsSideValues(body)
paths?.findAll { it.valueBeforeChecking() instanceof Pattern }?.each {
Object element = JsonToJsonPathsConverter.readElement(testSideValues, it.keyBeforeChecking())
bodyMatchers << new YamlContract.BodyStubMatcher(
path: it.keyBeforeChecking(),
type: YamlContract.StubMatcherType.by_regex,
value: (it.valueBeforeChecking() as Pattern).pattern()
value: (it.valueBeforeChecking() as Pattern).pattern(),
regexType: regexType(element)
)
}
}
protected RegexType regexType(Object from) {
return regexType(from.class)
}
protected RegexType regexType(Class clazz) {
switch(clazz) {
case Boolean:
return RegexType.as_boolean
case Long:
return RegexType.as_long
case Short:
return RegexType.as_short
case Integer:
return RegexType.as_integer
case Float:
return RegexType.as_float
case Double:
return RegexType.as_double
default:
return RegexType.as_string
}
}
protected void setOutputBodyMatchers(DslProperty body, List<YamlContract.BodyTestMatcher> bodyMatchers) {
def testSideValues = MapConverter.getTestSideValues(body)
JsonPaths paths = new JsonToJsonPathsConverter().transformToJsonPathWithTestsSideValues(body)
paths?.findAll { it.valueBeforeChecking() instanceof Pattern }?.each {
Object element = JsonToJsonPathsConverter.readElement(testSideValues, it.keyBeforeChecking())
bodyMatchers << new YamlContract.BodyTestMatcher(
path: it.keyBeforeChecking(),
type: YamlContract.TestMatcherType.by_regex,
value: (it.valueBeforeChecking() as Pattern).pattern()
value: (it.valueBeforeChecking() as Pattern).pattern(),
regexType: regexType(element)
)
}
if (body?.serverValue instanceof Pattern) {
@@ -259,10 +292,12 @@ class ContractsToYaml {
protected void setInputHeadersMatchers(Headers headers, List<YamlContract.KeyValueMatcher> headerMatchers) {
headers?.asStubSideMap()?.each { String key, Object value ->
if (value instanceof Pattern) {
if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value)
headerMatchers << new YamlContract.KeyValueMatcher(
key: key,
regex: value.pattern(),
regex: property.pattern(),
regexType: regexType(property.clazz())
)
}
}
@@ -270,10 +305,12 @@ class ContractsToYaml {
protected void setOutputHeadersMatchers(Headers headers, List<YamlContract.TestHeaderMatcher> headerMatchers) {
headers?.asTestSideMap()?.each { String key, Object value ->
if (value instanceof Pattern) {
if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value)
headerMatchers << new YamlContract.TestHeaderMatcher(
key: key,
regex: value.pattern(),
regex: property.pattern(),
regexType: regexType(property.clazz())
)
} else if (value instanceof ExecutionProperty) {
headerMatchers << new YamlContract.TestHeaderMatcher(
@@ -283,7 +320,7 @@ class ContractsToYaml {
} else if (value instanceof NotToEscapePattern) {
headerMatchers << new YamlContract.TestHeaderMatcher(
key: key,
regex: value.serverValue.pattern(),
regex: ((Pattern) value.serverValue).pattern(),
)
}
}

View File

@@ -107,6 +107,12 @@ class YamlContract {
public PredefinedRegex predefined
public Integer minOccurrence
public Integer maxOccurrence
public RegexType regexType
}
@CompileStatic
enum RegexType {
as_integer, as_double, as_float, as_long, as_short, as_boolean, as_string
}
@CompileStatic
@@ -145,6 +151,7 @@ class YamlContract {
public Integer minOccurrence
public Integer maxOccurrence
public PredefinedRegex predefined
public RegexType regexType
}
@CompileStatic
@@ -155,6 +162,7 @@ class YamlContract {
public String regex
public PredefinedRegex predefined
public String command
public RegexType regexType
}
@CompileStatic
@@ -181,6 +189,7 @@ class YamlContract {
public String regex
public String command
public PredefinedRegex predefined
public RegexType regexType
}
@CompileStatic
@@ -191,6 +200,7 @@ class YamlContract {
public String regex
public String command
public PredefinedRegex predefined
public RegexType regexType
}
@CompileStatic

View File

@@ -535,35 +535,35 @@ class YamlToContracts {
RegexPatterns patterns = new RegexPatterns()
switch (predefinedRegex) {
case YamlContract.PredefinedRegex.only_alpha_unicode:
return patterns.onlyAlphaUnicode()
return patterns.onlyAlphaUnicode().pattern
case YamlContract.PredefinedRegex.number:
return patterns.number()
return patterns.number().pattern
case YamlContract.PredefinedRegex.any_double:
return patterns.aDouble()
return patterns.aDouble().pattern
case YamlContract.PredefinedRegex.any_boolean:
return patterns.anyBoolean()
return patterns.anyBoolean().pattern
case YamlContract.PredefinedRegex.ip_address:
return patterns.ipAddress()
return patterns.ipAddress().pattern
case YamlContract.PredefinedRegex.hostname:
return patterns.hostname()
return patterns.hostname().pattern
case YamlContract.PredefinedRegex.email:
return patterns.email()
return patterns.email().pattern
case YamlContract.PredefinedRegex.url:
return patterns.url()
return patterns.url().pattern
case YamlContract.PredefinedRegex.uuid:
return patterns.uuid()
return patterns.uuid().pattern
case YamlContract.PredefinedRegex.iso_date:
return patterns.isoDate()
return patterns.isoDate().pattern
case YamlContract.PredefinedRegex.iso_date_time:
return patterns.isoDateTime()
return patterns.isoDateTime().pattern
case YamlContract.PredefinedRegex.iso_time:
return patterns.isoTime()
return patterns.isoTime().pattern
case YamlContract.PredefinedRegex.iso_8601_with_offset:
return patterns.iso8601WithOffset()
return patterns.iso8601WithOffset().pattern
case YamlContract.PredefinedRegex.non_empty:
return patterns.nonEmpty()
return patterns.nonEmpty().pattern
case YamlContract.PredefinedRegex.non_blank:
return patterns.nonBlank()
return patterns.nonBlank().pattern
default:
throw new UnsupportedOperationException("The predefined regex [" + predefinedRegex + "] is unsupported. Use on of " + YamlContract.PredefinedRegex.values())
}

View File

@@ -27,7 +27,6 @@ import com.github.tomakehurst.wiremock.matching.StringValuePattern
import com.github.tomakehurst.wiremock.matching.UrlPattern
import groovy.json.JsonOutput
import groovy.json.StringEscapeUtils
import groovy.transform.CompileDynamic
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
@@ -42,6 +41,7 @@ import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.spec.internal.QueryParameters
import org.springframework.cloud.contract.spec.internal.RegexPatterns
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.ContentUtils
@@ -51,7 +51,6 @@ import org.springframework.cloud.contract.verifier.util.MapConverter
import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildGStringRegexpForStubSide
import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildJSONRegexpMatch
/**
* Converts a {@link Request} into {@link RequestPattern}
*
@@ -174,8 +173,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
private UrlPattern urlPattern() {
Object urlPath = urlPathOrUrlIfQueryPresent()
if (urlPath) {
if(urlPath instanceof Pattern) {
return WireMock.urlPathMatching(getStubSideValue(urlPath.toString()) as String)
if(urlPath instanceof Pattern || urlPath instanceof RegexProperty) {
return WireMock.urlPathMatching(getStubSideValue(new RegexProperty(urlPath).pattern()) as String)
} else {
return WireMock.urlPathEqualTo(getStubSideValue(urlPath.toString()) as String)
}
@@ -184,8 +183,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
throw new IllegalStateException("URL is required!")
}
Object url = getUrlIfGstring(request?.url?.clientValue)
if (url instanceof Pattern) {
return WireMock.urlMatching((url as Pattern).pattern())
if (url instanceof Pattern || url instanceof RegexProperty) {
return WireMock.urlMatching(new RegexProperty(url).pattern())
}
return WireMock.urlEqualTo(url.toString())
}
@@ -204,8 +203,12 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
private Object getUrlIfGstring(Object clientSide) {
if (clientSide instanceof GString) {
if (clientSide.values.any { getStubSideValue(it) instanceof Pattern }) {
return Pattern.compile(getStubSideValue(clientSide).toString())
if (clientSide.values.any {
def value = getStubSideValue(it)
return value instanceof Pattern || value instanceof RegexProperty
}) {
String string = getStubSideValue(clientSide).toString()
return new RegexProperty(Pattern.compile(string))
} else {
return getStubSideValue(clientSide).toString()
}
@@ -224,8 +227,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
private static ContentPattern convertToValuePattern(Object object, ContentType contentType) {
switch (object) {
case Pattern:
Pattern value = object as Pattern
return WireMock.matching(value.pattern())
case RegexProperty:
return WireMock.matching(new RegexProperty(object).pattern())
case OptionalProperty:
OptionalProperty value = object as OptionalProperty
return WireMock.matching(value.optionalPattern())
@@ -339,9 +342,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
return containsPattern(map.entrySet())
}
@CompileDynamic
private boolean containsPattern(Collection collection) {
return collection.collect(this.&containsPattern).inject('') { a, b -> a || b }
return collection.collect(this.&containsPattern).inject(false) { a, b -> a || b }
}
private boolean containsPattern(Object[] objects) {
@@ -360,6 +362,10 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
return true
}
private boolean containsPattern(RegexProperty pattern) {
return true
}
private boolean containsPattern(Object o) {
return false
}

View File

@@ -215,7 +215,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
return readyToCheck;
}
@Override public JsonVerifiable isInstanceOf(Class clazz)
@Override public MethodBufferingJsonVerifiable isInstanceOf(Class clazz)
throws IllegalStateException {
DelegatingJsonVerifiable readyToCheck = new FinishedDelegatingJsonVerifiable(
this.delegate.jsonPath(), this.delegate.isInstanceOf(clazz), this.methodsBuffer);

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.contract.verifier.util
import java.util.regex.Pattern
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.PathNotFoundException
@@ -24,17 +26,15 @@ import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import org.apache.commons.lang3.StringEscapeUtils
import org.springframework.cloud.contract.spec.internal.BodyMatcher
import org.springframework.cloud.contract.spec.internal.BodyMatchers
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.util.SerializationUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
/**
* I would like to apologize to anyone who is reading this class. Since JSON is a hectic structure
* this class is also hectic. The idea is to traverse the JSON structure and build a set of
@@ -96,6 +96,18 @@ class JsonToJsonPathsConverter {
return jsonCopy
}
/**
* Retrieves the value from JSON via json path
*
* @param json - parsed JSON
* @param jsonPath - json path
* @return matching part of the json
*/
static def readElement(def json, String jsonPath) {
DocumentContext context = JsonPath.parse(json)
return context.read(jsonPath)
}
/**
* Related to #391. The converted body looks different when done via the String notation than
* it does when done via a map notation. When working with String body and when matchers
@@ -169,8 +181,8 @@ class JsonToJsonPathsConverter {
@CompileStatic
static Object generatedValueIfNeeded(Object value) {
if (value instanceof Pattern) {
return StringEscapeUtils.escapeJava(new Xeger(((Pattern) value).pattern()).generate())
if (value instanceof RegexProperty) {
return ((RegexProperty) value).generateAndEscapeJavaStringIfNeeded()
}
return value
}

View File

@@ -65,6 +65,9 @@ public interface MethodBufferingJsonVerifiable
@Override
MethodBufferingJsonVerifiable matches(String value);
@Override
MethodBufferingJsonVerifiable isInstanceOf(Class clazz);
@Override
MethodBufferingJsonVerifiable isEqualTo(Boolean value);

View File

@@ -1333,7 +1333,8 @@ DATA
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('''.cookie("cookie-key", "[A-Za-z]+")''')
!test.contains('''.cookie("cookie-key", "[A-Za-z]+")''')
test.contains('''.cookie("cookie-key", "''')
test.contains('''assertThat(response.getCookies().get("cookie-key")).isNotNull();''')
test.contains('''assertThat(response.getCookies().get("cookie-key").getValue()).matches("[A-Za-z]+");''')
and:
@@ -1376,7 +1377,8 @@ DATA
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('''.cookie('cookie-key', '[A-Za-z]+')''')
!test.contains('''.cookie('cookie-key', '[A-Za-z]+')''')
test.contains('''.cookie('cookie-key', ''')
test.contains('''response.getCookies().get('cookie-key') != null''')
test.contains('''response.getCookies().get('cookie-key').getValue() ==~ java.util.regex.Pattern.compile('[A-Za-z]+')''')
and:

View File

@@ -528,7 +528,7 @@ DocumentContext parsedJson = JsonPath.parse(json);
body(
[
"name" : $(consumer(~/.+/), producer('string-1')),
"updatedTs" : $(consumer(~/\d{13}/), producer(1531916906000L)),
"updatedTs" : $(consumer(regex(~/1531916906000/).asLong())),
"isDisabled": $(consumer(regex(anyBoolean())), producer(true))
]
)
@@ -557,6 +557,7 @@ DocumentContext parsedJson = JsonPath.parse(json);
String test = blockBuilder.toString()
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test)
test.contains('''assertThatJson(parsedJson).field("['updatedTs']").isEqualTo(1531916906000L)''')
!test.contains('''"updatedTs":"1531916906000"''')
and:
stubMappingIsValidWireMockStub(contractDsl)
where:

View File

@@ -62,12 +62,12 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
]
])
bodyMatchers {
jsonPath('$.duck', byRegex("[0-9]{3}"))
jsonPath('$.duck', byRegex("[0-9]{3}").asInteger())
jsonPath('$.duck', byEquality())
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()).asString())
jsonPath('$.alpha', byEquality())
jsonPath('$.number', byRegex(number()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
jsonPath('$.number', byRegex(number()).asInteger())
jsonPath('$.aBoolean', byRegex(anyBoolean()).asBooleanType())
jsonPath('$.date', byDate())
jsonPath('$.dateTime', byTimestamp())
jsonPath('$.time', byTime())
@@ -111,18 +111,18 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
])
bodyMatchers {
// asserts the jsonpath value against manual regex
jsonPath('$.duck', byRegex("[0-9]{3}"))
jsonPath('$.duck', byRegex("[0-9]{3}").asInteger())
// asserts the jsonpath value against the provided value
jsonPath('$.duck', byEquality())
// asserts the jsonpath value against some default regex
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()).asString())
jsonPath('$.alpha', byEquality())
jsonPath('$.number', byRegex(number()))
jsonPath('$.positiveInteger', byRegex(anInteger()))
jsonPath('$.negativeInteger', byRegex(anInteger()))
jsonPath('$.positiveDecimalNumber', byRegex(aDouble()))
jsonPath('$.negativeDecimalNumber', byRegex(aDouble()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
jsonPath('$.number', byRegex(number()).asInteger())
jsonPath('$.positiveInteger', byRegex(anInteger()).asInteger())
jsonPath('$.negativeInteger', byRegex(anInteger()).asInteger())
jsonPath('$.positiveDecimalNumber', byRegex(aDouble()).asDouble())
jsonPath('$.negativeDecimalNumber', byRegex(aDouble()).asDouble())
jsonPath('$.aBoolean', byRegex(anyBoolean()).asBooleanType())
// asserts vs inbuilt time related regex
jsonPath('$.date', byDate())
jsonPath('$.dateTime', byTimestamp())

View File

@@ -2794,7 +2794,8 @@ DocumentContext parsedJson = JsonPath.parse(json);
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('''.cookie("cookie-key", "[A-Za-z]+")''')
!test.contains('''.cookie("cookie-key", "[A-Za-z]+")''')
test.contains('''.cookie("cookie-key", "''')
test.contains('''assertThat(response.getCookie("cookie-key")).isNotNull();''')
test.contains('''assertThat(response.getCookie("cookie-key")).matches("[A-Za-z]+");''')
and:
@@ -2845,7 +2846,8 @@ DocumentContext parsedJson = JsonPath.parse(json);
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('''.cookie("cookie-key", "[A-Za-z]+")''')
!test.contains('''.cookie("cookie-key", "[A-Za-z]+")''')
test.contains('''.cookie("cookie-key", "''')
test.contains('''response.cookie('cookie-key') != null''')
test.contains('''response.cookie('cookie-key') ==~ java.util.regex.Pattern.compile('[A-Za-z]+')''')
and:

View File

@@ -540,6 +540,7 @@ response:
- path: $.property2
type: by_regex
value: "[0-9]{3}"
regexType: as_integer
'''
Contract contractDsl = fromYaml(contract)
MethodBodyBuilder builder = methodBuilder(contractDsl)
@@ -929,10 +930,12 @@ request:
headers:
- key: 'Content-Type'
regex: 'application/json.*'
regexType: as_string
body:
- path: $.first_name
type: by_regex
value: '[\\p{L}]*'
regexType: as_string
- path: $.last_name
type: by_regex
value: '[\\p{L}]*'
@@ -1127,8 +1130,10 @@ request:
params:
- key: formParameter
regex: ".+"
regexType: as_string
- key: someBooleanParameter
predefined: any_boolean
regexType: as_boolean
named:
- paramName: file
fileName:
@@ -1191,6 +1196,7 @@ response:
- path: $.authorities[0]
type: by_regex
value: '^[a-zA-Z0-9_\\- ]+$'
regexType: as_string
'''
Contract contractDsl = fromYaml(contract)
MethodBodyBuilder builder = new HttpSpockMethodRequestProcessingBodyBuilder(contractDsl, properties, generatedClassDataForMethod)

View File

@@ -33,7 +33,6 @@ import org.springframework.cloud.contract.spec.internal.RegexPatterns
import org.springframework.cloud.contract.spec.internal.Url
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
import org.springframework.cloud.contract.verifier.util.MapConverter
/**
* @author Marcin Grzejszczak
* @author Tim Ysewyn
@@ -125,7 +124,7 @@ class YamlContractConverterSpec extends Specification {
contract.request.body.clientValue == [foo: "bar"]
contract.request.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo'
contract.request.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.request.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
contract.request.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar'
and:
contract.response.status.clientValue == 200
if (yamlFile == ymlWithRest) contract.response.delay.clientValue == 1000 else !contract.response.delay
@@ -138,7 +137,7 @@ class YamlContractConverterSpec extends Specification {
contract.response.body.clientValue == [foo2: "bar", foo3: "baz", nullValue: null]
contract.response.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo2'
contract.response.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.response.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
contract.response.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar'
contract.response.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.foo3'
contract.response.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND
contract.response.bodyMatchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)')
@@ -217,29 +216,29 @@ class YamlContractConverterSpec extends Specification {
MatchingStrategy.Type.ABSENT, null)
contract.request.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck'
contract.request.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.request.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
contract.request.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}'
contract.request.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck'
contract.request.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
contract.request.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha'
contract.request.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
contract.request.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha'
contract.request.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
contract.request.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number'
contract.request.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
contract.request.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
contract.request.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
contract.request.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date'
contract.request.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
contract.request.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
contract.request.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
contract.request.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
contract.request.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
contract.request.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time'
contract.request.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
contract.request.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
contract.request.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern()
contract.request.bodyMatchers.jsonPathRegexMatchers[9].path() == "\$.['key'].['complex.key']"
contract.request.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.EQUALITY
contract.request.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin'
@@ -258,29 +257,29 @@ class YamlContractConverterSpec extends Specification {
contract.response.status.clientValue == 200
contract.response.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck'
contract.response.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.response.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
contract.response.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}'
contract.response.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck'
contract.response.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
contract.response.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha'
contract.response.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
contract.response.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha'
contract.response.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
contract.response.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number'
contract.response.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
contract.response.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
contract.response.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
contract.response.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date'
contract.response.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
contract.response.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
contract.response.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
contract.response.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
contract.response.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
contract.response.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time'
contract.response.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
contract.response.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
contract.response.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern()
contract.response.bodyMatchers.jsonPathRegexMatchers[9].path() == '$.valueWithTypeMatch'
contract.response.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.TYPE
contract.response.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin'
@@ -329,57 +328,57 @@ class YamlContractConverterSpec extends Specification {
((Pattern) it.clientValue).pattern == "application/json.*" && it.serverValue == "application/json" }
contract.input.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck'
contract.input.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.input.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
contract.input.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}'
contract.input.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck'
contract.input.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
contract.input.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha'
contract.input.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
contract.input.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha'
contract.input.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
contract.input.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number'
contract.input.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
contract.input.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
contract.input.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
contract.input.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date'
contract.input.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
contract.input.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
contract.input.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
contract.input.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
contract.input.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
contract.input.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time'
contract.input.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
contract.input.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
contract.input.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern()
contract.input.bodyMatchers.jsonPathRegexMatchers[9].path() == "\$.['key'].['complex.key']"
contract.input.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.EQUALITY
and:
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == '[0-9]{3}'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[2].value().pattern() == patterns.onlyAlphaUnicode().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[4].value().pattern() == patterns.number().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[5].value().pattern() == patterns.anyBoolean().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[6].value().pattern() == patterns.isoDate().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[7].value().pattern() == patterns.isoDateTime().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[8].value().pattern() == patterns.isoTime().pattern()
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[9].path() == '$.valueWithTypeMatch'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.TYPE
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin'
@@ -462,7 +461,7 @@ class YamlContractConverterSpec extends Specification {
contract.input.messageBody.clientValue == [foo: "bar"]
contract.input.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.bar'
contract.input.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.input.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
contract.input.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar'
and:
contract.outputMessage.assertThat.toString() == "baz()"
contract.outputMessage.headers.entries.find { it.name == "foo2" &&
@@ -474,7 +473,7 @@ class YamlContractConverterSpec extends Specification {
contract.outputMessage.body.clientValue == [foo2: "bar", foo3: "baz"]
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo2'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value().pattern() == 'bar'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.foo3'
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)')

View File

@@ -906,7 +906,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
stubMappingIsValidWireMockStub(json)
}
def "should not allow regexp in url for server value"() {
def "should not allow not matching query param for server value"() {
when:
org.springframework.cloud.contract.spec.Contract.make {
request {
@@ -924,7 +924,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
}
then:
def e = thrown(IllegalStateException)
e.message.contains "Url can't be a pattern for the server side"
e.message.contains "Query parameter 'age' can't be of a matching type: NOT_MATCHING for the server side"
}
def "should not allow regexp in query parameter for server value"() {