Fixed invalid cloning of a json; fixes gh-763
This commit is contained in:
@@ -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('/', '\\\\/')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user