Merge branch 'master' into 2.0.x
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user