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

@@ -801,7 +801,7 @@ following matching possibilities:
** `byEquality()`: The value taken from the consumer's request via the provided JSON Path must be
equal to the value provided in the contract.
** `byRegex(...)`: The value taken from the consumer's request via the provided JSON Path must
match the regex.
match the regex. You can also pass the type of the expected matched value (e.g. `asString()`, `asLong()` etc.)
** `byDate()`: The value taken from the consumer's request via the provided JSON Path must
match the regex for an ISO Date value.
** `byTimestamp()`: The value taken from the consumer's request via the provided JSON Path must
@@ -848,6 +848,7 @@ For YAML the structure of a matcher looks like this
- path: $.foo
type: by_regex
value: bar
regexType: as_string
----
Or if you want to use one of the predefined regular expressions
@@ -882,6 +883,16 @@ Below you can find the allowed list of `type`s.
** `by_command`
** `by_null`
You can also define which type the regular expression corresponds to via the `regexType` field. Below you can find the allowed list of regular expression types:
* as_integer
* as_double
* as_float,
* as_long
* as_short
* as_boolean
* as_string
Consider the following example:
.Groovy DSL

View File

@@ -74,7 +74,7 @@ public class LoanApplicationServiceTests {
// when:
int count = service.countAllFrauds();
// then:
assertThat(count).isEqualTo(200);
assertThat(count).isGreaterThanOrEqualTo(200);
}
@Test

View File

@@ -12,7 +12,7 @@ import org.springframework.cloud.contract.spec.Contract
response {
status OK()
body([
count: 200
count: $(regex("[2-9][0-9][0-9]").asInteger())
])
headers {
contentType("application/json")

View File

@@ -21,7 +21,6 @@ import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Matching strategy of dynamic parts of the body.
*
@@ -58,15 +57,18 @@ class BodyMatchers {
return new MatchingTypeValue(MatchingType.TIMESTAMP, this.regexPatterns.isoDateTime())
}
MatchingTypeValue byRegex(String regex) {
assert regex
return new MatchingTypeValue(MatchingType.REGEX, regex)
RegexMatchingTypeValue byRegex(String regex) {
return byRegex(Pattern.compile(regex))
}
// Backward compatibility with RegexPatterns
MatchingTypeValue byRegex(Pattern regex) {
RegexMatchingTypeValue byRegex(RegexProperty regex) {
assert regex
return new MatchingTypeValue(MatchingType.REGEX, regex)
return new RegexMatchingTypeValue(MatchingType.REGEX, regex)
}
RegexMatchingTypeValue byRegex(Pattern regex) {
assert regex
return new RegexMatchingTypeValue(MatchingType.REGEX, new RegexProperty(regex))
}
MatchingTypeValue byEquality() {
@@ -130,6 +132,61 @@ class JsonPathBodyMatcher implements BodyMatcher {
}
}
/**
* Matching type with corresponding values
*/
@ToString(includePackage = false)
@EqualsAndHashCode
class RegexMatchingTypeValue extends MatchingTypeValue {
RegexMatchingTypeValue(MatchingType type, Object value, Integer minTypeOccurrence, Integer maxTypeOccurrence) {
super(type, value, minTypeOccurrence, maxTypeOccurrence)
}
RegexMatchingTypeValue(MatchingType type, Object value) {
super(type, value)
}
RegexMatchingTypeValue asInteger() {
return typed(Integer)
}
private RegexMatchingTypeValue typed(Class clazz) {
assert this.value instanceof RegexProperty
RegexProperty regexProperty = (RegexProperty) this.value
return new RegexMatchingTypeValue(
this.type, new RegexProperty(regexProperty.clientValue,
regexProperty.serverValue, clazz),
this.minTypeOccurrence, this.maxTypeOccurrence
)
}
RegexMatchingTypeValue asDouble() {
return typed(Double)
}
RegexMatchingTypeValue asFloat() {
return typed(Float)
}
RegexMatchingTypeValue asLong() {
return typed(Long)
}
RegexMatchingTypeValue asShort() {
return typed(Short)
}
RegexMatchingTypeValue asString() {
return typed(String)
}
RegexMatchingTypeValue asBooleanType() {
return typed(Boolean)
}
}
/**
* Matching type with corresponding values
*/

View File

@@ -114,19 +114,27 @@ class Common {
return value(server, client)
}
Pattern regex(String regex) {
return Pattern.compile(regex)
RegexProperty regex(String regex) {
return regexProperty(Pattern.compile(regex))
}
RegexProperty regex(RegexProperty regex) {
return regex
}
// Backward compatibility with RegexPatterns
Pattern regex(Pattern regex) {
return regex
RegexProperty regex(Pattern regex) {
return regexProperty(regex)
}
OptionalProperty optional(Object object) {
return new OptionalProperty(object)
}
RegexProperty regexProperty(Object object) {
return new RegexProperty(object)
}
ExecutionProperty execute(String commandToExecute) {
return new ExecutionProperty(commandToExecute)
}

View File

@@ -16,16 +16,13 @@
package org.springframework.cloud.contract.spec.internal
import groovy.util.logging.Commons
import groovy.util.logging.Slf4j
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import repackaged.nl.flotsam.xeger.Xeger
import groovy.util.logging.Commons
/**
* Represents an input for messaging. The input can be a message or some
* action inside the application.
@@ -89,17 +86,31 @@ class Input extends Common {
}
DslProperty value(ClientDslProperty client) {
Object clientValue = client.clientValue
if (client.clientValue instanceof Pattern) {
clientValue = new Xeger(((Pattern)client.clientValue).pattern()).generate()
Object dynamicValue = client.clientValue
Object concreteValue = client.serverValue
if (dynamicValue instanceof RegexProperty) {
return dynamicValue.dynamicClientConcreteProducer()
}
return new DslProperty(client.clientValue, clientValue)
return new DslProperty(dynamicValue, concreteValue)
}
DslProperty value(RegexProperty prop) {
return value(client(prop))
}
DslProperty $(RegexProperty prop) {
return value(client(prop))
}
DslProperty $(ClientDslProperty client) {
return value(client)
}
@Override
RegexProperty regexProperty(Object object) {
return new RegexProperty(object).dynamicClientConcreteProducer()
}
@EqualsAndHashCode(includeFields = true, callSuper = true)
@ToString(includeSuper = true)
static class BodyType extends DslProperty {

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.contract.spec.internal
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.transform.ToString
/**
@@ -41,7 +40,12 @@ class OptionalProperty implements Serializable {
* in an optional function
*/
String optionalPattern() {
return "($value)?"
return "(${value()})?"
}
String value() {
return this.value instanceof RegexProperty ?
((RegexProperty) this.value).pattern.pattern() : this.value
}
protected Pattern optionalPatternValue() {

View File

@@ -16,17 +16,13 @@
package org.springframework.cloud.contract.spec.internal
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import groovy.util.logging.Commons
import groovy.util.logging.Slf4j
import org.apache.commons.lang3.StringEscapeUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
/**
* Represents an output for messaging. Used for verifying
* the body and headers that are sent.
@@ -83,26 +79,57 @@ class OutputMessage extends Common {
this.assertThat = new ExecutionProperty(assertThat)
}
/**
* @deprecated - use the server dsl property
*/
@Deprecated
DslProperty value(ClientDslProperty clientDslProperty) {
Object clientValue = clientDslProperty.clientValue
// for the output messages ran via stub runner,
// entries have to have fixed values
if (clientDslProperty.clientValue instanceof Pattern) {
clientValue = StringEscapeUtils.escapeJava(new Xeger(((Pattern)clientDslProperty.clientValue).pattern()).generate())
}
return new DslProperty(clientValue, clientDslProperty.clientValue)
return value(new ServerDslProperty(clientDslProperty.serverValue, clientDslProperty.clientValue))
}
DslProperty value(ServerDslProperty serverDslProperty) {
Object concreteValue = serverDslProperty.clientValue
Object dynamicValue = serverDslProperty.serverValue
// for the output messages ran via stub runner,
// entries have to have fixed values
if (dynamicValue instanceof RegexProperty) {
return dynamicValue
.concreteClientEscapedDynamicProducer()
}
return new DslProperty(concreteValue, dynamicValue)
}
/**
* @deprecated - use the server dsl property
*/
@Deprecated
DslProperty $(ClientDslProperty client) {
return value(client)
}
DslProperty $(ServerDslProperty property) {
return value(property)
}
DslProperty $(Pattern pattern) {
return value(client(pattern))
return value(new RegexProperty(pattern))
}
DslProperty $(RegexProperty pattern) {
return value(pattern)
}
DslProperty value(RegexProperty pattern) {
return value(producer(pattern))
}
DslProperty $(OptionalProperty property) {
return value(client(property.optionalPatternValue()))
return value(producer(property.optionalPatternValue()))
}
@Override
RegexProperty regexProperty(Object object) {
return new RegexProperty(object).concreteClientDynamicProducer()
}
/**

View File

@@ -60,80 +60,80 @@ class RegexPatterns {
return Pattern.compile(values.collect({"^$it\$"}).join("|"))
}
Pattern onlyAlphaUnicode() {
return ONLY_ALPHA_UNICODE
RegexProperty onlyAlphaUnicode() {
return new RegexProperty(ONLY_ALPHA_UNICODE).asString()
}
Pattern alphaNumeric() {
return ALPHA_NUMERIC
RegexProperty alphaNumeric() {
return new RegexProperty(ALPHA_NUMERIC).asString()
}
Pattern number() {
return NUMBER
RegexProperty number() {
return new RegexProperty(NUMBER).asDouble()
}
Pattern positiveInt() {
return POSITIVE_INT
RegexProperty positiveInt() {
return new RegexProperty(POSITIVE_INT).asInteger()
}
Pattern anyBoolean() {
return TRUE_OR_FALSE
RegexProperty anyBoolean() {
return new RegexProperty(TRUE_OR_FALSE).asBooleanType()
}
Pattern anInteger() {
return INTEGER
RegexProperty anInteger() {
return new RegexProperty(INTEGER).asInteger()
}
Pattern aDouble() {
return DOUBLE
RegexProperty aDouble() {
return new RegexProperty(DOUBLE).asDouble()
}
Pattern ipAddress() {
return IP_ADDRESS
RegexProperty ipAddress() {
return new RegexProperty(IP_ADDRESS).asString()
}
Pattern hostname() {
return HOSTNAME_PATTERN
RegexProperty hostname() {
return new RegexProperty(HOSTNAME_PATTERN).asString()
}
Pattern email() {
return EMAIL
RegexProperty email() {
return new RegexProperty(EMAIL).asString()
}
Pattern url() {
return URL
RegexProperty url() {
return new RegexProperty(URL).asString()
}
Pattern httpsUrl() {
return HTTPS_URL
RegexProperty httpsUrl() {
return new RegexProperty(HTTPS_URL).asString()
}
Pattern uuid(){
return UUID
RegexProperty uuid(){
return new RegexProperty(UUID).asString()
}
Pattern isoDate() {
return ANY_DATE
RegexProperty isoDate() {
return new RegexProperty(ANY_DATE).asString()
}
Pattern isoDateTime() {
return ANY_DATE_TIME
RegexProperty isoDateTime() {
return new RegexProperty(ANY_DATE_TIME).asString()
}
Pattern isoTime() {
return ANY_TIME
RegexProperty isoTime() {
return new RegexProperty(ANY_TIME).asString()
}
Pattern iso8601WithOffset() {
return ISO8601_WITH_OFFSET
RegexProperty iso8601WithOffset() {
return new RegexProperty(ISO8601_WITH_OFFSET).asString()
}
Pattern nonEmpty() {
return NON_EMPTY
RegexProperty nonEmpty() {
return new RegexProperty(NON_EMPTY).asString()
}
Pattern nonBlank() {
return NON_BLANK
RegexProperty nonBlank() {
return new RegexProperty(NON_BLANK).asString()
}
// end::regexps[]
@@ -150,7 +150,7 @@ class RegexPatterns {
if (contentType == null) {
return '.*'
}
if (contentType instanceof Pattern) {
if (contentType instanceof RegexProperty) {
return contentType.pattern()
}
return contentType.toString()
@@ -163,7 +163,7 @@ class RegexPatterns {
class UrlHelper {
/**
* Example: "http". Also called 'protocol'.
* Scheme component is optional, even though the RFC doesn't make it optional. Since this regex is validating a
* Scheme component is optional, even though the RFC doesn't make it optional. Since ((RegexProperty) this regex is validating a
* submitted callback url, which determines where the browser will navigate to after a successful authentication,
* the browser will use http or https for the scheme by default.
* Not borrowed from dperini in order to allow any scheme type.

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* 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.
*
*/
package org.springframework.cloud.contract.spec.internal
import java.util.regex.Matcher
import java.util.regex.Pattern
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import org.apache.commons.text.StringEscapeUtils
import repackaged.nl.flotsam.xeger.Xeger
/**
* Represents a regular expression property
*
* @since 2.1.0
*/
@TypeChecked
@EqualsAndHashCode
@ToString(includePackage = false, includeNames = true)
class RegexProperty extends DslProperty {
final Pattern pattern
private final Class clazz
RegexProperty(Object value) {
this(value, value, null)
}
RegexProperty(Object client, Object server) {
this(client, server, null)
}
RegexProperty(Object client, Object server, Class clazz) {
super(client, server)
boolean clientDynamic = client instanceof Pattern ||
client instanceof RegexProperty
boolean serverDynamic = server instanceof Pattern ||
server instanceof RegexProperty
if (!clientDynamic && !serverDynamic) {
throw new IllegalStateException("Neither client not server side is dynamic")
}
Object dynamicValue = clientDynamic ? client : server
if (dynamicValue instanceof Pattern) {
this.pattern = dynamicValue
this.clazz = clazz ?: String
} else if (dynamicValue instanceof RegexProperty) {
RegexProperty regexProperty = ((RegexProperty) dynamicValue)
this.pattern = regexProperty.pattern
this.clazz = clazz ?: regexProperty.clazz
} else {
this.clazz = clazz
}
}
Matcher matcher(CharSequence input) {
return this.pattern.matcher(input)
}
String pattern() {
return this.pattern.pattern()
}
Class clazz() {
return this.class
}
RegexProperty asInteger() {
return new RegexProperty(this.clientValue, this.serverValue, Integer)
}
RegexProperty asDouble() {
return new RegexProperty(this.clientValue, this.serverValue, Double)
}
RegexProperty asFloat() {
return new RegexProperty(this.clientValue, this.serverValue, Float)
}
RegexProperty asLong() {
return new RegexProperty(this.clientValue, this.serverValue, Long)
}
RegexProperty asShort() {
return new RegexProperty(this.clientValue, this.serverValue, Short)
}
RegexProperty asString() {
return new RegexProperty(this.clientValue, this.serverValue, String)
}
RegexProperty asBooleanType() {
return new RegexProperty(this.clientValue, this.serverValue, Boolean)
}
Object generate() {
String generatedValue = new Xeger(this.pattern.pattern()).generate()
switch (this.clazz) {
case Integer: return Integer.parseInt(generatedValue)
case Double: return Double.parseDouble(generatedValue)
case Float: return Float.parseFloat(generatedValue)
case Long: return Long.parseLong(generatedValue)
case Short: return Short.parseShort(generatedValue)
case Boolean: return Boolean.parseBoolean(generatedValue)
default: return generatedValue
}
}
Object generateAndEscapeJavaStringIfNeeded() {
Object generated = generate()
if (isNumber()) {
return generated
}
return StringEscapeUtils.escapeJava(generated as String)
}
private boolean isNumber() {
return Number.isAssignableFrom(this.clazz)
}
RegexProperty dynamicClientConcreteProducer() {
return new RegexProperty(this.pattern, generate(), this.clazz)
}
RegexProperty concreteClientDynamicProducer() {
return new RegexProperty(generate(), this.pattern, this.clazz)
}
RegexProperty concreteClientEscapedDynamicProducer() {
return new RegexProperty(generateAndEscapeJavaStringIfNeeded(), this.pattern, this.clazz)
}
RegexProperty dynamicClientEscapedConcreteProducer() {
return new RegexProperty(this.pattern, generateAndEscapeJavaStringIfNeeded(), this.clazz)
}
boolean equals(o) {
if (this.is(o)) return true
if (getClass() != o.class) return false
if (!super.equals(o)) return false
RegexProperty that = (RegexProperty) o
if (this.clazz != that.clazz) return false
if (this.pattern != that.pattern) return false
return true
}
int hashCode() {
int result = super.hashCode()
result = 31 * result + (this.pattern != null ? pattern.hashCode() : 0)
result = 31 * result + (this.clazz != null ? clazz.hashCode() : 0)
return result
}
@Override
String toString() {
return this.pattern()
}
}

View File

@@ -23,8 +23,6 @@ import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import groovy.util.logging.Commons
import org.apache.commons.lang3.StringEscapeUtils
import repackaged.nl.flotsam.xeger.Xeger
import org.springframework.cloud.contract.spec.util.RegexpUtils
/**
@@ -208,13 +206,22 @@ class Request extends Common {
}
DslProperty value(ClientDslProperty client) {
Object clientValue = client.clientValue
if (client.clientValue instanceof Pattern && client.isSingleValue()) {
clientValue = StringEscapeUtils.escapeJava(new Xeger(((Pattern)client.clientValue).pattern()).generate())
} else if (client.clientValue instanceof Pattern && !client.isSingleValue()) {
clientValue = client.serverValue
Object concreteValue = client.serverValue
Object dynamicValue = client.clientValue
if (dynamicValue instanceof RegexProperty && client.isSingleValue()) {
return dynamicValue.dynamicClientEscapedConcreteProducer()
} else if (concreteValue instanceof RegexProperty && !client.isSingleValue()) {
concreteValue = dynamicValue
}
return new DslProperty(client.clientValue, clientValue)
return new DslProperty(dynamicValue, concreteValue)
}
DslProperty $(RegexProperty property) {
return value(property)
}
DslProperty value(RegexProperty property) {
return value(client(property))
}
DslProperty $(ClientDslProperty client) {
@@ -222,13 +229,18 @@ class Request extends Common {
}
DslProperty value(Pattern client) {
return value(new ClientDslProperty(client))
return value(new RegexProperty(client))
}
DslProperty $(Pattern client) {
return value(client)
}
@Override
RegexProperty regexProperty(Object object) {
return new RegexProperty(object).dynamicClientConcreteProducer()
}
/**
* @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too
*/
@@ -246,7 +258,7 @@ class Request extends Common {
@Override
DslProperty value(ClientDslProperty client, ServerDslProperty server) {
if (server.clientValue instanceof Pattern) {
if (server.clientValue instanceof RegexProperty) {
throw new IllegalStateException("You can't have a regular expression for the request on the server side")
}
return super.value(client, server)
@@ -254,7 +266,7 @@ class Request extends Common {
@Override
DslProperty value(ServerDslProperty server, ClientDslProperty client) {
if (server.clientValue instanceof Pattern) {
if (server.clientValue instanceof RegexProperty) {
throw new IllegalStateException("You can't have a regular expression for the request on the server side")
}
return super.value(server, client)

View File

@@ -23,7 +23,6 @@ import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
import groovy.util.logging.Commons
import repackaged.nl.flotsam.xeger.Xeger
import org.springframework.cloud.contract.spec.util.RegexpUtils
/**
@@ -109,13 +108,12 @@ class Response extends Common {
}
DslProperty value(ServerDslProperty server) {
Object value = server.clientValue
if (server.clientValue instanceof Pattern && server.isSingleValue()) {
value = new Xeger(((Pattern)server.clientValue).pattern()).generate()
} else if (server.clientValue instanceof Pattern && !server.isSingleValue()) {
value = server.serverValue
Object dynamicValue = server.serverValue
Object concreteValue = server.clientValue
if (dynamicValue instanceof RegexProperty && server.isSingleValue()) {
return ((RegexProperty) dynamicValue).concreteClientDynamicProducer()
}
return new DslProperty(value, server.serverValue)
return new DslProperty(concreteValue, dynamicValue)
}
DslProperty $(ServerDslProperty server) {
@@ -123,13 +121,26 @@ class Response extends Common {
}
DslProperty value(Pattern server) {
return value(new RegexProperty(server))
}
DslProperty value(RegexProperty server) {
return value(new ServerDslProperty(server))
}
DslProperty $(Pattern server) {
DslProperty $(RegexProperty server) {
return value(server)
}
DslProperty $(Pattern server) {
return value(new RegexProperty(server))
}
@Override
RegexProperty regexProperty(Object object) {
return new RegexProperty(object).concreteClientDynamicProducer()
}
/**
* @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too
*/
@@ -151,7 +162,7 @@ class Response extends Common {
@Override
DslProperty value(ClientDslProperty client, ServerDslProperty server) {
if (client.clientValue instanceof Pattern) {
if (client.clientValue instanceof RegexProperty) {
throw new IllegalStateException("You can't have a regular expression for the response on the client side")
}
return super.value(client, server)
@@ -159,7 +170,7 @@ class Response extends Common {
@Override
DslProperty value(ServerDslProperty server, ClientDslProperty client) {
if (client.clientValue instanceof Pattern) {
if (client.clientValue instanceof RegexProperty) {
throw new IllegalStateException("You can't have a regular expression for the response on the client side")
}
return super.value(server, client)

View File

@@ -16,16 +16,12 @@
package org.springframework.cloud.contract.spec.internal
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import org.codehaus.groovy.runtime.GStringImpl
import repackaged.nl.flotsam.xeger.Xeger
import static org.springframework.cloud.contract.spec.util.ValidateUtils.validateServerValueIsAvailable
/**
* Represents a URL that may contain query parameters
*
@@ -50,13 +46,13 @@ class Url extends DslProperty {
private static Object testUrl(Object url) {
if (url instanceof GString) {
boolean anyPattern = url.values.any { it instanceof Pattern }
boolean anyPattern = url.values.any { it instanceof RegexProperty }
if (!anyPattern) {
return url
}
String newUrl = new GStringImpl(
url.values.collect { it instanceof Pattern ?
new Xeger(it.pattern()).generate() : it
url.values.collect { it instanceof RegexProperty ?
it.generate() : it
} as String[],
Arrays.copyOf(url.strings, url.strings.length) as String[]
).toString()

View File

@@ -14,7 +14,84 @@ class InputSpec extends Specification {
input.with {
property = $(consumer(regex("[0-9]{5}")))
}
def value = Integer.valueOf(property.serverValue as String)
def generatedValue = property.serverValue
generatedValue instanceof String
def value = Integer.valueOf(generatedValue as String)
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Double'() {
given:
Input input = new Input()
DslProperty property
when:
input.with {
property = $(consumer(regex("[0-9]{5}").asDouble()))
}
def value = property.serverValue
value instanceof Double
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Short'() {
given:
Input input = new Input()
DslProperty property
when:
input.with {
property = $(consumer(regex("[0-9]{1}").asShort()))
}
def value = property.serverValue
value instanceof Short
then:
value >= 0
value <= 9
}
def 'should set property when using the $() convenience method for Long'() {
given:
Input input = new Input()
DslProperty property
when:
input.with {
property = $(consumer(regex("[0-9]{5}").asLong()))
}
def value = property.serverValue
value instanceof Long
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Integer'() {
given:
Input input = new Input()
DslProperty property
when:
input.with {
property = $(consumer(regex("[0-9]{5}").asInteger()))
}
def value = property.serverValue
value instanceof Integer
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Float'() {
given:
Input input = new Input()
DslProperty property
when:
input.with {
property = $(consumer(regex("[0-9]{5}").asFloat()))
}
def value = property.serverValue
value instanceof Float
then:
value >= 0
value <= 99_999

View File

@@ -8,13 +8,90 @@ class OutputMessageSpec extends Specification {
def 'should set property when using the $() convenience method'() {
given:
Input input = new Input()
OutputMessage contract = new OutputMessage()
DslProperty property
when:
input.with {
property = $(consumer(regex("[0-9]{5}")))
contract.with {
property = $(producer(regex("[0-9]{5}")))
}
def value = Integer.valueOf(property.serverValue as String)
def generatedValue = property.clientValue
generatedValue instanceof String
def value = Integer.valueOf(generatedValue as String)
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Double'() {
given:
OutputMessage contract = new OutputMessage()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asDouble()))
}
def value = property.clientValue
value instanceof Double
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Short'() {
given:
OutputMessage contract = new OutputMessage()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{1}").asShort()))
}
def value = property.clientValue
value instanceof Short
then:
value >= 0
value <= 9
}
def 'should set property when using the $() convenience method for Long'() {
given:
OutputMessage contract = new OutputMessage()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asLong()))
}
def value = property.clientValue
value instanceof Long
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Integer'() {
given:
OutputMessage contract = new OutputMessage()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asInteger()))
}
def value = property.clientValue
value instanceof Integer
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Float'() {
given:
OutputMessage contract = new OutputMessage()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asFloat()))
}
def value = property.clientValue
value instanceof Float
then:
value >= 0
value <= 99_999

View File

@@ -8,30 +8,106 @@ class RequestSpec extends Specification {
def 'should throw exception when on request side a value contains regex for server'() {
given:
Request request = new Request()
Request contract = new Request()
when:
request.with {
contract.with {
value(consumer("foo"), producer(regex("foo")))
}
then:
thrown(IllegalStateException)
when:
request.with {
contract.with {
value(producer(regex("foo")), consumer("foo"))
}
then:
thrown(IllegalStateException)
}
def 'should generate a value if only regex is passed for client'() {
def 'should set property when using the $() convenience method'() {
given:
Request request = new Request()
Request contract = new Request()
DslProperty property
when:
request.with {
property = value(consumer(regex("[0-9]{5}")))
contract.with {
property = $(consumer(regex("[0-9]{5}")))
}
def value = Integer.valueOf(property.serverValue as String)
def generatedValue = property.serverValue
generatedValue instanceof String
def value = Integer.valueOf(generatedValue as String)
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Double'() {
given:
Request contract = new Request()
DslProperty property
when:
contract.with {
property = $(consumer(regex("[0-9]{5}").asDouble()))
}
def value = property.serverValue
value instanceof Double
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Short'() {
given:
Request contract = new Request()
DslProperty property
when:
contract.with {
property = $(consumer(regex("[0-9]{1}").asShort()))
}
def value = property.serverValue
value instanceof Short
then:
value >= 0
value <= 9
}
def 'should set property when using the $() convenience method for Long'() {
given:
Request contract = new Request()
DslProperty property
when:
contract.with {
property = $(consumer(regex("[0-9]{5}").asLong()))
}
def value = property.serverValue
value instanceof Long
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Integer'() {
given:
Request contract = new Request()
DslProperty property
when:
contract.with {
property = $(consumer(regex("[0-9]{5}").asInteger()))
}
def value = property.serverValue
value instanceof Integer
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Float'() {
given:
Request contract = new Request()
DslProperty property
when:
contract.with {
property = $(consumer(regex("[0-9]{5}").asFloat()))
}
def value = property.serverValue
value instanceof Float
then:
value >= 0
value <= 99_999

View File

@@ -1,9 +1,6 @@
package org.springframework.cloud.contract.spec.internal
import spock.lang.Specification
import java.util.regex.Pattern
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
@@ -26,18 +23,95 @@ class ResponseSpec extends Specification {
thrown(IllegalStateException)
}
def 'should generate a value if only regex is passed for server'() {
def 'should set property when using the $() convenience method'() {
given:
Response request = new Response()
Response contract = new Response()
DslProperty property
when:
request.with {
property = value(producer(regex("[0-9]{5}")))
contract.with {
property = $(producer(regex("[0-9]{5}")))
}
def value = Integer.valueOf(property.clientValue as String)
def generatedValue = property.clientValue
generatedValue instanceof String
def value = Integer.valueOf(generatedValue as String)
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Double'() {
given:
Response contract = new Response()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asDouble()))
}
def value = property.clientValue
value instanceof Double
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Short'() {
given:
Response contract = new Response()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{1}").asShort()))
}
def value = property.clientValue
value instanceof Short
then:
value >= 0
value <= 9
}
def 'should set property when using the $() convenience method for Long'() {
given:
Response contract = new Response()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asLong()))
}
def value = property.clientValue
value instanceof Long
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Integer'() {
given:
Response contract = new Response()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asInteger()))
}
def value = property.clientValue
value instanceof Integer
then:
value >= 0
value <= 99_999
}
def 'should set property when using the $() convenience method for Float'() {
given:
Response contract = new Response()
DslProperty property
when:
contract.with {
property = $(producer(regex("[0-9]{5}").asFloat()))
}
def value = property.clientValue
value instanceof Float
then:
value >= 0
value <= 99_999
(property.serverValue as Pattern).pattern() == '[0-9]{5}'
}
}

View File

@@ -37,6 +37,7 @@ import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.ContentType;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
@@ -133,8 +134,8 @@ class StubRunnerCamelPredicate implements Predicate {
BodyMatchers matchers = groovyDsl.getInput().getBodyMatchers();
matches = matchesForJsonPayload(groovyDsl, inputMessage, matchers, dslBody);
}
else if (dslBody instanceof Pattern && inputMessage instanceof String) {
Pattern pattern = (Pattern) dslBody;
else if (dslBody instanceof RegexProperty && inputMessage instanceof String) {
Pattern pattern = ((RegexProperty) dslBody).getPattern();
matches = pattern.matcher((String) inputMessage).matches();
bodyUnmatchedLog(dslBody, matches, pattern);
} else {
@@ -206,8 +207,8 @@ class StubRunnerCamelPredicate implements Predicate {
Object value = it.getClientValue();
Object valueInHeader = headers.get(name);
boolean matches;
if (value instanceof Pattern) {
Pattern pattern = (Pattern) value;
if (value instanceof RegexProperty) {
Pattern pattern = ((RegexProperty) value).getPattern();
matches = pattern.matcher(valueInHeader.toString()).matches();
}
else {
@@ -225,8 +226,8 @@ class StubRunnerCamelPredicate implements Predicate {
}
private String unmatchedText(Object expectedValue) {
return expectedValue instanceof Pattern
? "match pattern [" + ((Pattern) expectedValue).pattern() + "]"
return expectedValue instanceof RegexProperty
? "match pattern [" + ((RegexProperty) expectedValue).pattern() + "]"
: "be equal to [" + expectedValue + "]";
}

View File

@@ -36,6 +36,7 @@ import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.ContentType;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
@@ -151,8 +152,8 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
BodyMatchers matchers = groovyDsl.getInput().getBodyMatchers();
matches = matchesForJsonPayload(groovyDsl, inputMessage, matchers, dslBody);
}
else if (dslBody instanceof Pattern && inputMessage instanceof String) {
Pattern pattern = (Pattern) dslBody;
else if (dslBody instanceof RegexProperty && inputMessage instanceof String) {
Pattern pattern = ((RegexProperty) dslBody).getPattern();
matches = pattern.matcher((String) inputMessage).matches();
bodyUnmatchedLog(dslBody, matches, pattern);
} else {
@@ -224,8 +225,8 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
Object value = it.getClientValue();
Object valueInHeader = headers.get(name);
boolean matches;
if (value instanceof Pattern) {
Pattern pattern = (Pattern) value;
if (value instanceof RegexProperty) {
Pattern pattern = ((RegexProperty) value).getPattern();
matches = pattern.matcher(valueInHeader.toString()).matches();
}
else {
@@ -243,8 +244,8 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
}
private String unmatchedText(Object expectedValue) {
return expectedValue instanceof Pattern
? "match pattern [" + ((Pattern) expectedValue).pattern() + "]"
return expectedValue instanceof RegexProperty
? "match pattern [" + ((RegexProperty) expectedValue).pattern() + "]"
: "be equal to [" + expectedValue + "]";
}
}

View File

@@ -36,6 +36,7 @@ import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.ContentType;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
@@ -150,8 +151,8 @@ class StubRunnerStreamMessageSelector implements MessageSelector {
BodyMatchers matchers = groovyDsl.getInput().getBodyMatchers();
matches = matchesForJsonPayload(groovyDsl, inputMessage, matchers, dslBody);
}
else if (dslBody instanceof Pattern && inputMessage instanceof String) {
Pattern pattern = (Pattern) dslBody;
else if (dslBody instanceof RegexProperty && inputMessage instanceof String) {
Pattern pattern = ((RegexProperty) dslBody).getPattern();
matches = pattern.matcher((String) inputMessage).matches();
bodyUnmatchedLog(dslBody, matches, pattern);
} else {
@@ -223,8 +224,8 @@ class StubRunnerStreamMessageSelector implements MessageSelector {
Object value = it.getClientValue();
Object valueInHeader = headers.get(name);
boolean matches;
if (value instanceof Pattern) {
Pattern pattern = (Pattern) value;
if (value instanceof RegexProperty) {
Pattern pattern = ((RegexProperty) value).getPattern();
matches = pattern.matcher(valueInHeader.toString()).matches();
}
else {
@@ -242,8 +243,8 @@ class StubRunnerStreamMessageSelector implements MessageSelector {
}
private String unmatchedText(Object expectedValue) {
return expectedValue instanceof Pattern
? "match pattern [" + ((Pattern) expectedValue).pattern() + "]"
return expectedValue instanceof RegexProperty
? "match pattern [" + ((RegexProperty) expectedValue).pattern() + "]"
: "be equal to [" + expectedValue + "]";
}
}

View File

@@ -16,12 +16,14 @@
package org.springframework.cloud.contract.verifier.wiremock
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubMapping
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
import java.util.regex.Pattern
import spock.lang.Specification
import java.util.regex.Pattern
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockStubMapping
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
class WireMockToDslConverterSpec extends Specification {
@@ -148,7 +150,7 @@ class WireMockToDslConverterSpec extends Specification {
$groovyDsl
}""")
def b = expectedGroovyDsl
(a.first().request.url.clientValue as Pattern).pattern() == (b.request.url.clientValue as Pattern).pattern()
(a.first().request.url.clientValue as RegexProperty).pattern() == (b.request.url.clientValue as Pattern).pattern()
}
def 'should convert WireMock stub with response body containing integer'() {
@@ -401,7 +403,7 @@ class WireMockToDslConverterSpec extends Specification {
$groovyDsl
}""").first()
and:
(evaluatedGroovyDsl.request.body.clientValue as Pattern).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern()
(evaluatedGroovyDsl.request.body.clientValue as RegexProperty).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern()
}
def 'should convert WireMock stub with request body with equalToJson'() {
@@ -522,7 +524,7 @@ class WireMockToDslConverterSpec extends Specification {
$groovyDsl
}""").first()
and:
(evaluatedGroovyDsl.request.body.clientValue as Pattern).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern()
(evaluatedGroovyDsl.request.body.clientValue as RegexProperty).pattern() == (expectedGroovyDsl.request.body.clientValue as Pattern).pattern()
}
def 'should convert WireMock stub with priorities'() {

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.cloud.contract.verifier.spec.pact
import java.util.regex.Pattern
import au.com.dius.pact.model.generators.Category
import au.com.dius.pact.model.generators.DateGenerator
import au.com.dius.pact.model.generators.DateTimeGenerator
@@ -30,13 +32,13 @@ import au.com.dius.pact.model.generators.TimeGenerator
import au.com.dius.pact.model.generators.UuidGenerator
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.internal.Body
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.OutputMessage
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.verifier.util.ContentUtils
import java.util.regex.Pattern
/**
* @author Tim Ysewyn
* @Since 2.0.0
@@ -129,8 +131,9 @@ class ValueGeneratorConverter {
}
} else if (v instanceof DslProperty) {
traverse(v, dslPropertyValueProvider, path, generators, category)
} else if (v instanceof Pattern) {
switch (v.pattern()) {
} else if (v instanceof RegexProperty || v instanceof Pattern) {
RegexProperty regexProperty = new RegexProperty(v)
switch (regexProperty.pattern()) {
case INTEGER_PATTERN:
generators.addGenerator(category, path, new RandomIntGenerator(0, Integer.MAX_VALUE))
break
@@ -159,7 +162,7 @@ class ValueGeneratorConverter {
generators.addGenerator(category, path, RandomBooleanGenerator.INSTANCE)
break
default:
generators.addGenerator(category, path, new RegexGenerator(v.pattern()))
generators.addGenerator(category, path, new RegexGenerator(regexProperty.pattern()))
break
}
}

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"() {