Fixed invalid cloning of a json; fixes gh-763

This commit is contained in:
Marcin Grzejszczak
2018-10-23 22:13:26 +02:00
parent 15344a159c
commit edb3d97f3f
3 changed files with 176 additions and 14 deletions

View File

@@ -22,7 +22,9 @@ import com.jayway.jsonpath.PathNotFoundException
import com.toomuchcoding.jsonassert.JsonAssertion
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
@@ -30,6 +32,7 @@ import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.util.SerializationUtils
import repackaged.nl.flotsam.xeger.Xeger
import java.util.regex.Pattern
/**
@@ -125,15 +128,8 @@ class JsonToJsonPathsConverter {
// Doing a clone doesn't work for nested lists...
private static Object cloneBody(Object object) {
if (object instanceof List) {
byte[] serializedObject = SerializationUtils.serialize(object)
return SerializationUtils.deserialize(serializedObject)
}
try {
return object.clone()
} catch (CloneNotSupportedException e) {
return object
}
byte[] serializedObject = SerializationUtils.serialize(object)
return SerializationUtils.deserialize(serializedObject)
}
/**
@@ -168,17 +164,30 @@ class JsonToJsonPathsConverter {
return path.contains("['")
}
@CompileStatic
static Object generatedValueIfNeeded(Object value) {
if (value instanceof Pattern) {
return StringEscapeUtils.escapeJava(new Xeger(((Pattern) value).pattern()).generate())
}
return value
}
private static String createComparison(BodyMatcher bodyMatcher, Object value, def body) {
if (bodyMatcher.matchingType() == MatchingType.EQUALITY) {
Object convertedBody = body
if (!body) {
throw new IllegalStateException("Body hasn't been passed")
}
try {
Object retrievedValue = JsonPath.parse(body).read(bodyMatcher.path())
convertedBody = MapConverter.transformValues(body) {
return generatedValueIfNeeded(it)
}
Object retrievedValue = JsonPath.parse(convertedBody).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)
throw new IllegalStateException("Value [${bodyMatcher.path()}] not found in JSON [${JsonOutput.toJson(convertedBody)}]", e)
}
} else {
String convertedValue = value.toString().replace('/', '\\\\/')

View File

@@ -20,9 +20,6 @@ import groovy.json.JsonSlurper
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor
import org.springframework.cloud.contract.verifier.template.TemplateProcessor
import static org.springframework.cloud.contract.verifier.util.ContentUtils.getClientContentType
/**
* Converts an object into either client or server side representation.
* Iterates over the structure of an object (depending on whether it's an

View File

@@ -54,4 +54,160 @@ class WireMockResponseStubStrategySpec extends Specification {
assert body.get("positiveInt") instanceof Integer
assert body.get("double") instanceof BigDecimal
}
def "should convert patterns to proper value"() {
given:
def contract = Contract.make {
request {
method(GET())
urlPath(value(regex("/info/[0-9]"))) {
queryParameters {
parameter 'limit': $(consumer(equalTo('20')), producer(equalTo('10')))
parameter 'offset': $(consumer(containing("20")), producer(equalTo('20')))
parameter 'filter': 'email'
parameter 'sort': equalTo("name")
parameter 'age': $(consumer(notMatching("^\\w*\$")), producer('99'))
parameter 'name': $(consumer(matching('John.*')), producer('John.Doe'))
parameter 'email': 'bob@email.com'
parameter 'hello': $(consumer(matching('John.*')), producer(absent()))
parameter 'hello2': absent()
}
}
headers {
contentType(applicationJson())
header([
second: "value", third: $(anyAlphaNumeric())
])
}
body([
foo1 : $(c(regex("[0-9]")), p(1)),
foo2 : $(c(regex("[0-9]"))),
foo3 : $(anyAlphaNumeric()),
foo4 : value(regex(aDouble())),
foo5 : value(anyDouble()),
foo6 : "concrete",
duck : 123,
alpha : 'abc',
number : 123,
aBoolean : true,
date : '2017-01-01',
dateTime : '2017-01-01T01:23:45',
time : '01:02:34',
valueWithoutAMatcher: 'foo',
valueWithTypeMatch : 'string',
key : [
'complex.key': 'foo'
]
])
bodyMatchers {
jsonPath('$.duck', byRegex("[0-9]{3}"))
jsonPath('$.duck', byEquality())
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byEquality())
jsonPath('$.number', byRegex(number()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
jsonPath('$.date', byDate())
jsonPath('$.dateTime', byTimestamp())
jsonPath('$.time', byTime())
jsonPath("\$.['key'].['complex.key']", byEquality())
}
}
response {
status(OK())
headers {
contentType(applicationJson())
header([
second: "value", third: $(anyAlphaNumeric())
])
}
body([
foo1 : $(p(regex("[0-9]")), c(1)),
foo2 : $(p(regex("[0-9]"))),
foo3 : $(anyAlphaNumeric()),
foo4 : value(regex(aDouble())),
foo5 : value(anyDouble()),
foo6 : "concrete",
duck : 123,
alpha : 'abc',
number : 123,
positiveInteger : 1234567890,
negativeInteger : -1234567890,
positiveDecimalNumber: 123.4567890,
negativeDecimalNumber: -123.4567890,
aBoolean : true,
date : '2017-01-01',
dateTime : '2017-01-01T01:23:45',
time : "01:02:34",
valueWithoutAMatcher : 'foo',
valueWithTypeMatch : 'string',
valueWithMin : [
1, 2, 3
],
valueWithMax : [
1, 2, 3
],
valueWithMinMax : [
1, 2, 3
],
valueWithMinEmpty : [],
valueWithMaxEmpty : [],
key : [
'complex.key': 'foo'
],
nullValue : null
])
bodyMatchers {
// asserts the jsonpath value against manual regex
jsonPath('$.duck', byRegex("[0-9]{3}"))
// 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', 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()))
// asserts vs inbuilt time related regex
jsonPath('$.date', byDate())
jsonPath('$.dateTime', byTimestamp())
jsonPath('$.time', byTime())
// asserts that the resulting type is the same as in response body
jsonPath('$.valueWithTypeMatch', byType())
jsonPath('$.valueWithMin', byType {
// results in verification of size of array (min 1)
minOccurrence(1)
})
jsonPath('$.valueWithMax', byType {
// results in verification of size of array (max 3)
maxOccurrence(3)
})
jsonPath('$.valueWithMinMax', byType {
// results in verification of size of array (min 1 & max 3)
minOccurrence(1)
maxOccurrence(3)
})
jsonPath('$.valueWithMinEmpty', byType {
// results in verification of size of array (min 0)
minOccurrence(0)
})
jsonPath('$.valueWithMaxEmpty', byType {
// results in verification of size of array (max 0)
maxOccurrence(0)
})
// will execute a method `assertThatValueIsANumber`
jsonPath('$.duck', byCommand('assertThatValueIsANumber($it)'))
jsonPath("\$.['key'].['complex.key']", byEquality())
jsonPath('$.nullValue', byNull())
}
}
}
when:
def subject = new WireMockRequestStubStrategy(contract)
subject.buildClientRequestContent()
then:
noExceptionThrown()
}
}