Merge branch '1.0.x'
Stub / Test Matchers (#186) Without this change we're forcing users to embed their dynamic properties inside the body. For some this is natural and acceptable, but especially for the users coming from the Pact world this sounds bizarre. Also some other people have a problem with remembering who the consumer / producer is etc. With this change we're introducing the stubMatchers and testMatchers section. Thanks to this one can separate the body from defining the dynamic properties. Especially for Pact users this is more natural. Speaking of which this is a prerequisite for #96 fixes #185
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
/**
|
||||
* A jsonPathMatchers for the given path.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 1.0.3
|
||||
*/
|
||||
interface BodyMatcher {
|
||||
|
||||
/**
|
||||
* What kind of matching are we dealing with
|
||||
*/
|
||||
MatchingType matchingType()
|
||||
|
||||
/**
|
||||
* Path to the path. Example for JSON it will be JSON Path
|
||||
*/
|
||||
String path()
|
||||
|
||||
/**
|
||||
* Optional value that the given path should be checked against.
|
||||
* If there is no value then presence will be checked only together with
|
||||
* type check. Example if we expect a JSON Path path {@code $.a} to be matched
|
||||
* by type, the defined response body contained an integer but the actual one
|
||||
* contained a string then the assertion should fail
|
||||
*/
|
||||
String value()
|
||||
|
||||
/**
|
||||
* Min no of occurrence when matching by type. In all other cases it will be ignored
|
||||
*/
|
||||
Integer minTypeOccurrence()
|
||||
|
||||
/**
|
||||
* Max no of occurrence when matching by type. In all other cases it will be ignored
|
||||
*/
|
||||
Integer maxTypeOccurrence()
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.Canonical
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.ToString
|
||||
/**
|
||||
* Matching strategy of dynamic parts of the body.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 1.0.3
|
||||
*/
|
||||
@CompileStatic
|
||||
class BodyMatchers {
|
||||
private final RegexPatterns regexPatterns = new RegexPatterns()
|
||||
private final List<BodyMatcher> jsonPathRegexMatchers = []
|
||||
|
||||
void jsonPath(String path, MatchingTypeValue matchingType) {
|
||||
this.jsonPathRegexMatchers << new JsonPathBodyMatcher(path, matchingType)
|
||||
}
|
||||
|
||||
boolean hasMatchers() {
|
||||
return !this.jsonPathRegexMatchers.empty
|
||||
}
|
||||
|
||||
List<BodyMatcher> jsonPathMatchers() {
|
||||
return this.jsonPathRegexMatchers
|
||||
}
|
||||
|
||||
MatchingTypeValue byDate() {
|
||||
return new MatchingTypeValue(MatchingType.DATE, this.regexPatterns.isoDate())
|
||||
}
|
||||
|
||||
MatchingTypeValue byTime() {
|
||||
return new MatchingTypeValue(MatchingType.TIME, this.regexPatterns.isoTime())
|
||||
}
|
||||
|
||||
MatchingTypeValue byTimestamp() {
|
||||
return new MatchingTypeValue(MatchingType.TIMESTAMP, this.regexPatterns.isoDateTime())
|
||||
}
|
||||
|
||||
MatchingTypeValue byRegex(String regex) {
|
||||
assert regex
|
||||
return new MatchingTypeValue(MatchingType.REGEX, regex)
|
||||
}
|
||||
}
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Canonical
|
||||
@CompileStatic
|
||||
class JsonPathBodyMatcher implements BodyMatcher {
|
||||
String jsonPath
|
||||
MatchingTypeValue matchingTypeValue
|
||||
|
||||
@Override
|
||||
MatchingType matchingType() {
|
||||
return this.matchingTypeValue.type
|
||||
}
|
||||
|
||||
@Override
|
||||
String path() {
|
||||
return this.jsonPath
|
||||
}
|
||||
|
||||
@Override
|
||||
String value() {
|
||||
return this.matchingTypeValue.value
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer minTypeOccurrence() {
|
||||
return this.matchingTypeValue.minTypeOccurrence
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer maxTypeOccurrence() {
|
||||
return this.matchingTypeValue.maxTypeOccurrence
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matching type with corresponding values
|
||||
*/
|
||||
@Canonical
|
||||
class MatchingTypeValue {
|
||||
MatchingType type
|
||||
|
||||
/**
|
||||
* Value of regular expression
|
||||
*/
|
||||
String value
|
||||
|
||||
/**
|
||||
* Min occurrence when matching by type
|
||||
*/
|
||||
Integer minTypeOccurrence
|
||||
|
||||
/**
|
||||
* Max occurrence when matching by type
|
||||
*/
|
||||
Integer maxTypeOccurrence
|
||||
}
|
||||
@@ -40,6 +40,7 @@ class Input extends Common {
|
||||
Headers messageHeaders
|
||||
BodyType messageBody
|
||||
ExecutionProperty assertThat
|
||||
BodyMatchers matchers
|
||||
|
||||
Input() {}
|
||||
|
||||
@@ -104,6 +105,12 @@ class Input extends Common {
|
||||
void assertThat(String assertThat) {
|
||||
this.assertThat = new ExecutionProperty(assertThat)
|
||||
}
|
||||
|
||||
void stubMatchers(@DelegatesTo(BodyMatchers) Closure closure) {
|
||||
this.matchers = new BodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
closure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
/**
|
||||
* Represents the type of matching the should be done against
|
||||
* the body of the request or response.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 1.0.3
|
||||
*/
|
||||
@CompileStatic
|
||||
enum MatchingType {
|
||||
EQUALITY, TYPE, DATE, TIME, TIMESTAMP, REGEX
|
||||
}
|
||||
@@ -39,9 +39,8 @@ class OptionalProperty {
|
||||
return "($value)?"
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String toString() {
|
||||
return optionalPattern()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class OutputMessage extends Common {
|
||||
Headers headers
|
||||
DslProperty body
|
||||
ExecutionProperty assertThat
|
||||
ResponseBodyMatchers matchers
|
||||
|
||||
OutputMessage() {}
|
||||
|
||||
@@ -75,6 +76,12 @@ class OutputMessage extends Common {
|
||||
}
|
||||
return new DslProperty(value, server.serverValue)
|
||||
}
|
||||
|
||||
void testMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) {
|
||||
this.matchers = new ResponseBodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
closure()
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
@@ -94,3 +101,4 @@ class ClientOutputMessage extends OutputMessage {
|
||||
super(request)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ class Request extends Common {
|
||||
RequestHeaders headers
|
||||
Body body
|
||||
Multipart multipart
|
||||
BodyMatchers matchers
|
||||
|
||||
Request() {
|
||||
}
|
||||
@@ -204,6 +205,12 @@ class Request extends Common {
|
||||
return value(client)
|
||||
}
|
||||
|
||||
void stubMatchers(@DelegatesTo(BodyMatchers) Closure closure) {
|
||||
this.matchers = new BodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
closure()
|
||||
}
|
||||
|
||||
@Override
|
||||
DslProperty value(ClientDslProperty client, ServerDslProperty server) {
|
||||
if (server.clientValue instanceof Pattern) {
|
||||
|
||||
@@ -41,6 +41,7 @@ class Response extends Common {
|
||||
ResponseHeaders headers
|
||||
Body body
|
||||
boolean async
|
||||
ResponseBodyMatchers matchers
|
||||
|
||||
Response() {
|
||||
}
|
||||
@@ -111,6 +112,12 @@ class Response extends Common {
|
||||
return value(server)
|
||||
}
|
||||
|
||||
void testMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) {
|
||||
this.matchers = new ResponseBodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
closure()
|
||||
}
|
||||
|
||||
@Override
|
||||
DslProperty value(ClientDslProperty client, ServerDslProperty server) {
|
||||
if (client.clientValue instanceof Pattern) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.ToString
|
||||
|
||||
/**
|
||||
* Body matchers for the response side (output message, REST response)
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 1.0.3
|
||||
*/
|
||||
@CompileStatic
|
||||
@EqualsAndHashCode
|
||||
@ToString(includePackage = false)
|
||||
class ResponseBodyMatchers extends BodyMatchers {
|
||||
|
||||
MatchingTypeValue byType() {
|
||||
return new MatchingTypeValue(MatchingType.TYPE)
|
||||
}
|
||||
|
||||
MatchingTypeValue byType(@DelegatesTo(MatchingTypeValueHolder) Closure closure) {
|
||||
MatchingTypeValueHolder matchingTypeValue = new MatchingTypeValueHolder()
|
||||
closure.delegate = matchingTypeValue
|
||||
return closure() as MatchingTypeValue
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
class MatchingTypeValueHolder {
|
||||
MatchingTypeValue matchingTypeValue = new MatchingTypeValue()
|
||||
|
||||
MatchingTypeValue minOccurrence(int number) {
|
||||
this.matchingTypeValue.minTypeOccurrence = number
|
||||
return this.matchingTypeValue
|
||||
}
|
||||
|
||||
MatchingTypeValue maxOccurrence(int number) {
|
||||
this.matchingTypeValue.maxTypeOccurrence = number
|
||||
return this.matchingTypeValue
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user