Merge branch 'master' into 2.0.x

This commit is contained in:
Marcin Grzejszczak
2017-08-28 17:31:20 +02:00
3 changed files with 125 additions and 2 deletions

View File

@@ -522,7 +522,8 @@ match the regex for ISO Time
- `byType()` - the value taken from the response via the provided JSON Path needs to
be of the same type as the type defined in the body of the response in the contract.
`byType` can take a closure where you can set `minOccurrence` and `maxOccurrence`.
That way you can assert on the size of the collection.
That way you can assert on the size of the flattened collection. To check the size
of an unflattened collection, use a custom method via `byCommand(...)` testMatcher.
- `byCommand(...)` - the value taken from the response via the provided JSON Path will be
passed as an input to the custom method that you're providing. E.g. `byCommand('foo($it)')`
will result in calling a `foo` method to which the value matching the JSON Path will get
@@ -1241,4 +1242,4 @@ IMPORTANT: If you don't provide any implementation then the default one will be
If you provide `repositoryRoot` property or `workOffline` flag then Aether based
that will download stubs from a remote repo will be picked. If you don't provide these
values then the `ClasspathStubProvider` will be picked that will scan the classpath.
If you provide more than one, then the first one on the list will be picked.
If you provide more than one, then the first one on the list will be picked.

View File

@@ -50,6 +50,7 @@ class JsonToJsonPathsConverter {
private static final Boolean SERVER_SIDE = false
private static final Boolean CLIENT_SIDE = true
private static final String ANY_ARRAY_NOTATION_IN_JSONPATH = "[*]"
private final ContractVerifierConfigProperties configProperties
@@ -80,6 +81,7 @@ class JsonToJsonPathsConverter {
bodyMatchers.jsonPathMatchers().each { BodyMatcher matcher ->
try {
context.delete(matcher.path())
removeTrailingContainers(matcher, context)
} catch (RuntimeException e) {
if (log.isDebugEnabled()) {
log.debug("Exception occurred while trying to delete path [${matcher.path()}]", e)
@@ -90,6 +92,36 @@ class JsonToJsonPathsConverter {
return jsonCopy
}
/**
* 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
* are provided, even when all entries of a map / list got removed, the map / list itself
* remains. That leads to unnecessary creation of checks for empty collection. With this method
* we're checking if the JSON path matcher is related to array checking and we're trying to
* remove that trailing collection. All in all it's better to use the Groovy based notation for
* defining body...
*/
private static void removeTrailingContainers(BodyMatcher matcher, DocumentContext context) {
if (matcher.path().contains(ANY_ARRAY_NOTATION_IN_JSONPATH)) {
String pathWithoutAnyArray = matcher.path().substring(0, matcher.path().lastIndexOf(ANY_ARRAY_NOTATION_IN_JSONPATH))
def object = context.read(pathWithoutAnyArray)
if (object instanceof Iterable && containsOnlyEmptyElements(object)) {
context.delete(pathWithoutAnyArray)
}
}
}
private static boolean containsOnlyEmptyElements(Object object) {
return object.every {
if (it instanceof Map) {
return it.isEmpty()
} else if (it instanceof List) {
return it.isEmpty()
}
return false
}
}
// Doing a clone doesn't work for nested lists...
private static Object cloneBody(Object object) {
if (object instanceof List) {

View File

@@ -423,4 +423,94 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$'
}
@Issue("#391")
def "should work for matchers and body with multiline string for [#methodBuilderName]"() {
given:
Contract contractDsl = Contract.make {
request {
name "ISSUE 391"
method 'GET'
urlPath '/item/factsheet?size=2&page=1'
headers { header "accept", "application/...json" }
}
response {
status 200
body("""
{
"items": [
{
"id": "35309",
"title": "lorem ipsum"
}
]
}
""")
testMatchers {
jsonPath('$.items[*].id', byRegex(nonBlank()))
jsonPath('$.items[*].title', byRegex(nonBlank()))
jsonPath('$.items[*]', byType { minOccurrence(2); maxOccurrence(2) })
}
headers {header "content-type", "application/...json;charset=UTF-8"}
}
}
MethodBodyBuilder builder = methodBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
and:
builder.appendTo(blockBuilder)
String test = blockBuilder.toString()
when:
SyntaxChecker.tryToCompile(methodBuilderName, test)
then:
!test.contains('''assertThatJson(parsedJson).array("['items']").isEmpty()''')
where:
methodBuilderName | methodBuilder
"MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
"MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) }
"JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
}
@Issue("#391")
def "should work for matchers and body with multiline string with map body for [#methodBuilderName]"() {
given:
Contract contractDsl = Contract.make {
request {
name "ISSUE 391"
method 'GET'
urlPath '/item/factsheet?size=2&page=1'
headers { header "accept", "application/...json" }
}
response {
status 200
body([
"items": [
"id" : "35309",
"title": "lorem ipsum"
]
])
testMatchers {
jsonPath('$.items[*].id', byRegex(nonBlank()))
jsonPath('$.items[*].title', byRegex(nonBlank()))
jsonPath('$.items[*]', byType { minOccurrence(2); maxOccurrence(2) })
}
headers {header "content-type", "application/...json;charset=UTF-8"}
}
}
MethodBodyBuilder builder = methodBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
and:
builder.appendTo(blockBuilder)
String test = blockBuilder.toString()
when:
SyntaxChecker.tryToCompile(methodBuilderName, test)
then:
!test.contains('''assertThatJson(parsedJson).array("['items']").isEmpty()''')
where:
methodBuilderName | methodBuilder
"MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
"MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) }
"JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
}
}