diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy index 8cf641b8a8..dd75129279 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy @@ -85,20 +85,26 @@ class JsonToJsonPathsConverter { def jsonCopy = cloneBody(json) DocumentContext context = JsonPath.parse(jsonCopy) if (bodyMatchers?.hasMatchers()) { - bodyMatchers.matchers().each { BodyMatcher matcher -> + List pathsToDelete = [] + List paths = bodyMatchers.matchers().collect { it.path() } + paths.each { String path -> try { - def entry = entry(context, matcher.path()) + def entry = entry(context, path) if (entry != null) { - context.delete(matcher.path()) - removeTrailingContainers(matcher.path(), context) + context.delete(path) + pathsToDelete.add(path) } } catch (RuntimeException e) { if (log.isDebugEnabled()) { - log.debug("Exception occurred while trying to delete path [${matcher.path()}]", e) + log.debug("Exception occurred while trying to delete path [${path}]", e) } } } + pathsToDelete.sort(Collections.reverseOrder()) + pathsToDelete.each { + removeTrailingContainers(it, context) + } } return jsonCopy } @@ -137,38 +143,46 @@ class JsonToJsonPathsConverter { * defining body... */ private static boolean removeTrailingContainers(String matcherPath, DocumentContext context) { - Matcher matcher = ANY_ARRAY_NOTATION_IN_JSONPATH.matcher(matcherPath) - boolean containsArray = matcher.find() - String pathWithoutAnyArray = containsArray ? matcherPath. - substring(0, matcherPath.lastIndexOf(lastMatch(matcher))) : matcherPath - def object = entry(context, pathWithoutAnyArray) - // object got removed and it was the only element - // let's get its parent and see if it contains an empty element - if (isIterable(object) - && - containsOnlyEmptyElements(object) - && isNotRootArray(matcherPath)) { - String pathToDelete = pathToDelete(pathWithoutAnyArray) - context.delete(pathToDelete) - return pathToDelete.contains(DESCENDANT_OPERATOR) ? false : - removeTrailingContainers(pathToDelete, context) - } - else { - int lastIndexOfDot = matcherPath.lastIndexOf(".") - if (lastIndexOfDot == -1) { - return false - } - String lastParent = matcherPath.substring(0, lastIndexOfDot) - def lastParentObject = context.read(lastParent) - if (isIterable(lastParentObject) + try { + Matcher matcher = ANY_ARRAY_NOTATION_IN_JSONPATH.matcher(matcherPath) + boolean containsArray = matcher.find() + String pathWithoutAnyArray = containsArray ? matcherPath. + substring(0, matcherPath.lastIndexOf(lastMatch(matcher))) : matcherPath + def object = entry(context, pathWithoutAnyArray) + // object got removed and it was the only element + // let's get its parent and see if it contains an empty element + if (isIterable(object) && - containsOnlyEmptyElements(lastParentObject) - && isNotRoot(lastParent)) { - context.delete(lastParent) - return removeTrailingContainers(lastParent, context) + containsOnlyEmptyElements(object) + && isNotRootArray(matcherPath)) { + String pathToDelete = pathToDelete(pathWithoutAnyArray) + context.delete(pathToDelete) + return pathToDelete.contains(DESCENDANT_OPERATOR) ? false : + removeTrailingContainers(pathToDelete, context) } + else { + int lastIndexOfDot = matcherPath.lastIndexOf(".") + if (lastIndexOfDot == -1) { + return false + } + String lastParent = matcherPath.substring(0, lastIndexOfDot) + def lastParentObject = context.read(lastParent) + if (isIterable(lastParentObject) + && + containsOnlyEmptyElements(lastParentObject) + && isNotRoot(lastParent)) { + context.delete(lastParent) + return removeTrailingContainers(lastParent, context) + } + } + return false + } + catch (RuntimeException e) { + if (log.isDebugEnabled()) { + log.debug("Exception occurred while trying to delete path [${matcherPath}]", e) + } + return false } - return false } private static String lastMatch(Matcher matcher) { diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy index 2977694041..44c325470a 100755 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy @@ -2753,6 +2753,64 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie } + @Issue("#1125") + def "should not generate assertions for [*] when all manual entries were passed"() { + given: + Contract contractDsl = Contract.make { + request { + method(GET()) + url '/operations' + body( + channedlId: "UC", + operations: [ + [ + parameters : [ + [ + name : "#POID", + value: '70000269814', + type : "DECIMAL" + ], + [ + name : "#OVID", + value: "3", + type : "DECIMAL" + ], + [ + name : "#CAMC", + value: "CC2PAY", + type : "CHAR" + ] + ] + ] + ] + ) + bodyMatchers { + jsonPath('$.operations[0].parameters[0].name', byEquality()) + jsonPath('$.operations[0].parameters[0].value', byRegex('[0-9]{11}')) + jsonPath('$.operations[0].parameters[0].type', byEquality()) + jsonPath('$.operations[0].parameters[1].name', byEquality()) + jsonPath('$.operations[0].parameters[1].value', byEquality()) + jsonPath('$.operations[0].parameters[1].type', byEquality()) + jsonPath('$.operations[0].parameters[2].name', byEquality()) + jsonPath('$.operations[0].parameters[2].value', byEquality()) + jsonPath('$.operations[0].parameters[2].type', byEquality()) + } + } + + response { + status 200 + } + } + when: + String wireMockStub = new WireMockStubStrategy("Test", + new ContractMetadata(null, false, 0, null, contractDsl), contractDsl) + .toWireMockClientStub() + + then: + !wireMockStub.contains('''$.['operations'][*]''') + stubMappingIsValidWireMockStub(wireMockStub) + } + @Issue("#1257") def "should work with null request element on the client side and optional stub entry"() { given: