Merge branch '1.0.x'
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.contract.verifier.builder
|
||||
|
||||
import com.jayway.jsonpath.JsonPath
|
||||
import com.jayway.jsonpath.PathNotFoundException
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TypeChecked
|
||||
@@ -311,19 +312,17 @@ abstract class MethodBodyBuilder {
|
||||
bb.startBlock()
|
||||
// for the rest we'll do JsonPath matching in brute force
|
||||
bodyMatchers.jsonPathMatchers().each {
|
||||
if (it.value()) {
|
||||
if (it.value() || it.matchingType() == MatchingType.EQUALITY) {
|
||||
String comparisonMethod = it.matchingType() == MatchingType.EQUALITY ? "isEqualTo" : "matches"
|
||||
String valueAsParam = it.value() instanceof String ? quotedAndEscaped(it.value().toString()) : it.value().toString()
|
||||
String classToCastTo = "${it.value().class.simpleName}.class"
|
||||
Object retrievedValue = value(copiedBody, it)
|
||||
String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString()
|
||||
String classToCastTo = "${retrievedValue.class.simpleName}.class"
|
||||
String path = quotedAndEscaped(it.path())
|
||||
String method = "assertThat(parsedJson.read(${path}, ${classToCastTo})).${comparisonMethod}(${valueAsParam})"
|
||||
bb.addLine(postProcessJsonPathCall(method))
|
||||
addColonIfRequired(bb)
|
||||
} else {
|
||||
Object elementFromBody = JsonPath.parse(copiedBody).read(it.path())
|
||||
if (!elementFromBody) {
|
||||
throw new IllegalStateException("Entry for the provided JSON path [${it.path()}] doesn't exist in the body [${JsonOutput.toJson(copiedBody)}]")
|
||||
}
|
||||
Object elementFromBody = value(copiedBody, it)
|
||||
if (it.minTypeOccurrence() || it.maxTypeOccurrence()) {
|
||||
checkType(bb, it, elementFromBody)
|
||||
String method = "assertThat(parsedJson.read(${quotedAndEscaped(it.path())}, java.util.Collection.class).size()).${sizeCheckMethod(it)}"
|
||||
@@ -338,6 +337,21 @@ abstract class MethodBodyBuilder {
|
||||
processBodyElement(bb, "", convertedResponseBody)
|
||||
}
|
||||
|
||||
protected Object value(def body, BodyMatcher bodyMatcher) {
|
||||
if (bodyMatcher.matchingType() == MatchingType.EQUALITY || !bodyMatcher.value()) {
|
||||
return retrieveObjectByPath(body, bodyMatcher.path())
|
||||
}
|
||||
return bodyMatcher.value()
|
||||
}
|
||||
|
||||
protected Object retrieveObjectByPath(def body, String path) {
|
||||
try {
|
||||
return JsonPath.parse(body).read(path)
|
||||
} catch (PathNotFoundException e) {
|
||||
throw new IllegalStateException("Entry for the provided JSON path [${path}] doesn't exist in the body [${JsonOutput.toJson(body)}]", e)
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkType(BlockBuilder bb, BodyMatcher it, Object elementFromBody) {
|
||||
String method = "assertThat((Object) parsedJson.read(${quotedAndEscaped(it.path())})).isInstanceOf(${classToCheck(elementFromBody).name}.class)"
|
||||
bb.addLine(postProcessJsonPathCall(method))
|
||||
|
||||
@@ -75,8 +75,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
}
|
||||
ContentType contentType = tryToGetContentType(request.body.clientValue, request.headers)
|
||||
if (contentType == ContentType.JSON) {
|
||||
def body = getMatchingStrategyFromBody(request.body)?.clientValue
|
||||
body = JsonToJsonPathsConverter.removeMatchingJsonPaths(body, request.matchers)
|
||||
def originalBody = getMatchingStrategyFromBody(request.body)?.clientValue
|
||||
def body = JsonToJsonPathsConverter.removeMatchingJsonPaths(originalBody, request.matchers)
|
||||
JsonPaths values = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(body)
|
||||
if (values.empty && !request.matchers?.hasMatchers()) {
|
||||
requestPattern.withRequestBody(WireMock.equalToJson(JsonOutput.toJson(getMatchingStrategy(request.body.clientValue).clientValue), false, false))
|
||||
@@ -87,7 +87,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
}
|
||||
if (request.matchers?.hasMatchers()) {
|
||||
request.matchers.jsonPathMatchers().each {
|
||||
String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it)
|
||||
String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it, originalBody)
|
||||
requestPattern.withRequestBody(WireMock.matchingJsonPath(newPath.replace("\\\\", "\\")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,18 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util
|
||||
|
||||
import com.jayway.jsonpath.DocumentContext
|
||||
import com.jayway.jsonpath.JsonPath
|
||||
import com.jayway.jsonpath.PathNotFoundException
|
||||
import com.toomuchcoding.jsonassert.JsonAssertion
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.util.logging.Slf4j
|
||||
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.*
|
||||
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
|
||||
|
||||
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
|
||||
@@ -71,12 +70,14 @@ class JsonToJsonPathsConverter {
|
||||
* @return json with removed entries
|
||||
*/
|
||||
static def removeMatchingJsonPaths(def json, BodyMatchers bodyMatchers) {
|
||||
def jsonCopy = json.clone()
|
||||
DocumentContext context = JsonPath.parse(jsonCopy)
|
||||
if (bodyMatchers?.hasMatchers()) {
|
||||
bodyMatchers.jsonPathMatchers().each { BodyMatcher matcher ->
|
||||
JsonPath.parse(json).delete(matcher.path())
|
||||
context.delete(matcher.path())
|
||||
}
|
||||
}
|
||||
return json
|
||||
return jsonCopy
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,23 +87,31 @@ class JsonToJsonPathsConverter {
|
||||
* @param bodyMatcher
|
||||
* @return JSON path that checks the regex for its last element
|
||||
*/
|
||||
static String convertJsonPathAndRegexToAJsonPath(BodyMatcher bodyMatcher) {
|
||||
static String convertJsonPathAndRegexToAJsonPath(BodyMatcher bodyMatcher, def body = null) {
|
||||
String path = bodyMatcher.path()
|
||||
Object value = bodyMatcher.value()
|
||||
if (!value) {
|
||||
if (value == null && bodyMatcher.matchingType() != MatchingType.EQUALITY) {
|
||||
return path
|
||||
}
|
||||
int lastIndexOfDot = path.lastIndexOf(".")
|
||||
String toLastDot = path.substring(0, lastIndexOfDot)
|
||||
String fromLastDot = path.substring(lastIndexOfDot + 1)
|
||||
String comparison = createComparison(bodyMatcher, value)
|
||||
String comparison = createComparison(bodyMatcher, value, body)
|
||||
return "${toLastDot}[?(@.${fromLastDot} ${comparison})]"
|
||||
}
|
||||
|
||||
private static String createComparison(BodyMatcher bodyMatcher, Object value) {
|
||||
private static String createComparison(BodyMatcher bodyMatcher, Object value, def body) {
|
||||
if (bodyMatcher.matchingType() == MatchingType.EQUALITY) {
|
||||
String wrappedValue = value instanceof Number ? value : "'${value.toString()}'"
|
||||
return "== ${wrappedValue}"
|
||||
if (!body) {
|
||||
throw new IllegalStateException("Body hasn't been passed")
|
||||
}
|
||||
try {
|
||||
Object retrievedValue = JsonPath.parse(body).read(bodyMatcher.path())
|
||||
String wrappedValue = retrievedValue instanceof Number ? retrievedValue : "'${retrievedValue.toString()}'"
|
||||
return "== ${wrappedValue}"
|
||||
} catch (PathNotFoundException e) {
|
||||
throw new IllegalStateException("Value [${bodyMatcher.path()}] not found in JSON [${JsonOutput.toJson(body)}]", e)
|
||||
}
|
||||
} else {
|
||||
return "=~ /(${value})/"
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
])
|
||||
stubMatchers {
|
||||
jsonPath('$.duck', byRegex("[0-9]{3}"))
|
||||
jsonPath('$.duck', byValue(123))
|
||||
jsonPath('$.duck', byEquality())
|
||||
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
|
||||
jsonPath('$.alpha', byValue("abc"))
|
||||
jsonPath('$.alpha', byEquality())
|
||||
jsonPath('$.number', byRegex(number()))
|
||||
jsonPath('$.aBoolean', byRegex(anyBoolean()))
|
||||
jsonPath('$.date', byDate())
|
||||
@@ -90,10 +90,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
// asserts the jsonpath value against manual regex
|
||||
jsonPath('$.duck', byRegex("[0-9]{3}"))
|
||||
// asserts the jsonpath value against the provided value
|
||||
jsonPath('$.duck', byValue(123))
|
||||
jsonPath('$.duck', byEquality())
|
||||
// asserts the jsonpath value against some default regex
|
||||
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
|
||||
jsonPath('$.alpha', byValue("abc"))
|
||||
jsonPath('$.alpha', byEquality())
|
||||
jsonPath('$.number', byRegex(number()))
|
||||
jsonPath('$.aBoolean', byRegex(anyBoolean()))
|
||||
// asserts vs inbuilt time related regex
|
||||
|
||||
@@ -766,17 +766,19 @@ class JsonToJsonPathsConverterSpec extends Specification {
|
||||
def "should convert a json path with value to a equality checking json path without quotes for numbers"() {
|
||||
given:
|
||||
String jsonPath = '$.a.b.c.d'
|
||||
Integer value = 1234
|
||||
and:
|
||||
def body = [ a: [ b: [ c: [ d: 1234 ] ] ] ]
|
||||
expect:
|
||||
'$.a.b.c[?(@.d == 1234)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, value))
|
||||
'$.a.b.c[?(@.d == 1234)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body)
|
||||
}
|
||||
|
||||
def "should convert a json path with value to a equality checking json path with quotes for strings"() {
|
||||
given:
|
||||
String jsonPath = '$.a.b.c.d'
|
||||
String value = "foo"
|
||||
and:
|
||||
def body = [ a: [ b: [ c: [ d: "foo" ] ] ] ]
|
||||
expect:
|
||||
'$.a.b.c[?(@.d == \'foo\')]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, value))
|
||||
'$.a.b.c[?(@.d == \'foo\')]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body)
|
||||
}
|
||||
|
||||
def "should return the path if no value is provided"() {
|
||||
@@ -786,6 +788,30 @@ class JsonToJsonPathsConverterSpec extends Specification {
|
||||
'$.a.b.c.d' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.REGEX, jsonPath, null))
|
||||
}
|
||||
|
||||
def "should throw an exception when null body is passed to check for equality"() {
|
||||
given:
|
||||
String jsonPath = '$.a.b.c.d'
|
||||
and:
|
||||
def body = null
|
||||
when:
|
||||
JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body)
|
||||
then:
|
||||
IllegalStateException e = thrown(IllegalStateException)
|
||||
e.message.contains("Body")
|
||||
}
|
||||
|
||||
def "should throw an exception when nonexisting jsonpath is passed to check for equality"() {
|
||||
given:
|
||||
String jsonPath = '$.a.b.c.d'
|
||||
and:
|
||||
def body = [ foo: "bar" ]
|
||||
when:
|
||||
JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body)
|
||||
then:
|
||||
IllegalStateException e = thrown(IllegalStateException)
|
||||
e.message.contains("not found")
|
||||
}
|
||||
|
||||
private BodyMatcher matcher(final MatchingType matchingType, final String jsonPath, final Object value) {
|
||||
return new BodyMatcher() {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user