Merge branch '2.0.x'
# Conflicts: # docker/spring-cloud-contract-docker/Dockerfile # docker/spring-cloud-contract-stub-runner-docker/Dockerfile # spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
FROM ubuntu:18.10
|
||||
|
||||
ARG SDKMAN_JAVA_INSTALLATION=8.0.201-zulu
|
||||
ARG SDKMAN_JAVA_INSTALLATION=8.0.202-zulu
|
||||
|
||||
MAINTAINER Marcin Grzejszczak <mgrzejszczak@pivotal.io>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM ubuntu:18.10
|
||||
|
||||
ARG SDKMAN_JAVA_INSTALLATION=8.0.201-zulu
|
||||
ARG SDKMAN_JAVA_INSTALLATION=8.0.202-zulu
|
||||
|
||||
MAINTAINER Marcin Grzejszczak <mgrzejszczak@pivotal.io>
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import groovy.transform.CompileDynamic
|
||||
import groovy.xml.XmlUtil
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import repackaged.nl.flotsam.xeger.Xeger
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@@ -33,6 +34,7 @@ import static org.apache.commons.text.StringEscapeUtils.escapeJava
|
||||
* Converts WireMock stubs into the DSL format
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @author Konstantin Shevchuk
|
||||
*/
|
||||
@CompileDynamic
|
||||
class WireMockToDslConverter {
|
||||
@@ -123,7 +125,7 @@ class WireMockToDslConverter {
|
||||
return wrapWithMultilineGString(JsonOutput.prettyPrint(responseBody))
|
||||
} catch (Exception jsonException) {
|
||||
try {
|
||||
def xml = new XmlSlurper().parseText(responseBody)
|
||||
def xml = ContentUtils.getXmlSlurperWithDefaultErrorHandler().parseText(responseBody)
|
||||
return wrapWithMultilineGString(XmlUtil.serialize(responseBody))
|
||||
} catch (Exception xmlException) {
|
||||
return wrapWithMultilineGString(responseBody)
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util
|
||||
@@ -36,6 +35,7 @@ import org.springframework.cloud.contract.spec.internal.Headers
|
||||
import org.springframework.cloud.contract.spec.internal.MatchingStrategy
|
||||
import org.springframework.cloud.contract.spec.internal.NamedProperty
|
||||
import org.springframework.cloud.contract.spec.internal.OptionalProperty
|
||||
import org.xml.sax.helpers.DefaultHandler
|
||||
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJava
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJson
|
||||
@@ -49,8 +49,9 @@ import static org.springframework.cloud.contract.verifier.util.ContentType.UNKNO
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*
|
||||
* @author Konstantin Shevchuk
|
||||
* @since 1.0.0
|
||||
*
|
||||
*/
|
||||
@CompileStatic
|
||||
class ContentUtils {
|
||||
@@ -119,7 +120,7 @@ class ContentUtils {
|
||||
return JSON
|
||||
} catch(JsonException e) {
|
||||
try {
|
||||
new XmlSlurper().parseText(extractValueForXML(bodyAsValue, GET_STUB_SIDE).toString())
|
||||
getXmlSlurperWithDefaultErrorHandler().parseText(extractValueForXML(bodyAsValue, GET_STUB_SIDE).toString())
|
||||
return ContentType.XML
|
||||
} catch (Exception ignored) {
|
||||
extractValueForGString(bodyAsValue, GET_STUB_SIDE)
|
||||
@@ -134,7 +135,7 @@ class ContentUtils {
|
||||
return JSON
|
||||
} catch(JsonException e) {
|
||||
try {
|
||||
new XmlSlurper().parseText(bodyAsValue)
|
||||
getXmlSlurperWithDefaultErrorHandler().parseText(bodyAsValue)
|
||||
return ContentType.XML
|
||||
} catch (Exception ignored) {
|
||||
return UNKNOWN
|
||||
@@ -234,7 +235,7 @@ class ContentUtils {
|
||||
bodyAsValue.strings.clone() as String[]
|
||||
)
|
||||
// try to convert it to XML
|
||||
new XmlSlurper().parseText(impl.toString())
|
||||
getXmlSlurperWithDefaultErrorHandler().parseText(impl.toString())
|
||||
return impl
|
||||
}
|
||||
|
||||
@@ -464,7 +465,7 @@ class ContentUtils {
|
||||
gString.strings.clone() as String[]
|
||||
)
|
||||
try {
|
||||
new XmlSlurper().parseText(stringWithoutValues.toString())
|
||||
getXmlSlurperWithDefaultErrorHandler().parseText(stringWithoutValues.toString())
|
||||
return true
|
||||
} catch (Exception ignored) {
|
||||
// Not XML
|
||||
@@ -547,4 +548,15 @@ class ContentUtils {
|
||||
}
|
||||
return contentType
|
||||
}
|
||||
/**
|
||||
* Creates new {@link XmlSlurper} with default error handler.
|
||||
*
|
||||
* @return XmlSlurper with default error handler
|
||||
*/
|
||||
static XmlSlurper getXmlSlurperWithDefaultErrorHandler() {
|
||||
XmlSlurper xmlSlurper = new XmlSlurper()
|
||||
xmlSlurper.setErrorHandler(new DefaultHandler())
|
||||
return xmlSlurper
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,8 +84,11 @@ class JsonToJsonPathsConverter {
|
||||
if (bodyMatchers?.hasMatchers()) {
|
||||
bodyMatchers.matchers().each { BodyMatcher matcher ->
|
||||
try {
|
||||
context.delete(matcher.path())
|
||||
removeTrailingContainers(matcher, context)
|
||||
def entry = entry(context, matcher.path())
|
||||
if (entry != null) {
|
||||
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)
|
||||
@@ -96,6 +99,17 @@ class JsonToJsonPathsConverter {
|
||||
return jsonCopy
|
||||
}
|
||||
|
||||
private static def entry(DocumentContext context, String path) {
|
||||
try {
|
||||
return context.read(path)
|
||||
} catch (Exception ex) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Exception occurred while trying to retrieve element via path [${path}]", ex)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value from JSON via json path
|
||||
*
|
||||
@@ -118,22 +132,26 @@ class JsonToJsonPathsConverter {
|
||||
* 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)) {
|
||||
String pathToDelete = pathToDelete(pathWithoutAnyArray)
|
||||
context.delete(pathToDelete)
|
||||
} else {
|
||||
String lastParent = matcher.path().substring(0, matcher.path().lastIndexOf("."))
|
||||
def lastParentObject = context.read(lastParent)
|
||||
if (lastParentObject instanceof Iterable && containsOnlyEmptyElements(lastParentObject)) {
|
||||
context.delete(lastParent)
|
||||
}
|
||||
String pathWithoutAnyArray = matcher.path().contains(ANY_ARRAY_NOTATION_IN_JSONPATH) ? matcher.path().substring(0, matcher.path().lastIndexOf(ANY_ARRAY_NOTATION_IN_JSONPATH)) : matcher.path()
|
||||
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)) {
|
||||
String pathToDelete = pathToDelete(pathWithoutAnyArray)
|
||||
context.delete(pathToDelete)
|
||||
} else {
|
||||
String lastParent = matcher.path().substring(0, matcher.path().lastIndexOf("."))
|
||||
def lastParentObject = context.read(lastParent)
|
||||
if (isIterable(lastParentObject) && containsOnlyEmptyElements(lastParentObject)) {
|
||||
context.delete(lastParent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isIterable(Object object) {
|
||||
return object instanceof Iterable || object instanceof Map
|
||||
}
|
||||
|
||||
private static String pathToDelete(String pathWithoutAnyArray) {
|
||||
// we can't remove root
|
||||
return pathWithoutAnyArray == '$' ? '$[*]' : pathWithoutAnyArray
|
||||
|
||||
@@ -1174,4 +1174,49 @@ DocumentContext parsedJson = JsonPath.parse(json);
|
||||
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties, classDataForMethod) }
|
||||
}
|
||||
|
||||
@Issue("#727")
|
||||
def "should not leave empty arrays [#methodBuilderName]"() {
|
||||
given:
|
||||
Contract contractDsl = Contract.make {
|
||||
request {
|
||||
method 'GET'
|
||||
url '/list'
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body(
|
||||
[
|
||||
content: [
|
||||
one: "two",
|
||||
two: "two",
|
||||
three: [
|
||||
six: "seven"
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
bodyMatchers {
|
||||
jsonPath('$.content.three.six', byRegex(".*seven.*"))
|
||||
jsonPath('$.content.one', byRegex(".*two.*"))
|
||||
}
|
||||
}
|
||||
}
|
||||
MethodBodyBuilder builder = methodBuilder(contractDsl)
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ")
|
||||
when:
|
||||
builder.appendTo(blockBuilder)
|
||||
then:
|
||||
String test = blockBuilder.toString()
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test)
|
||||
!test.contains('''.isEmpty()''')
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(contractDsl)
|
||||
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) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.springframework.cloud.contract.verifier.util
|
||||
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
import spock.lang.Specification
|
||||
import org.xml.sax.helpers.DefaultHandler
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Konstantin Shevchuk
|
||||
*/
|
||||
class ContentUtilsSpec extends Specification {
|
||||
|
||||
@@ -22,4 +23,11 @@ class ContentUtilsSpec extends Specification {
|
||||
expect:
|
||||
"test" == ContentUtils.GET_TEST_SIDE(dslProperty)
|
||||
}
|
||||
|
||||
def "should return XmlSlurper with default error handler"() {
|
||||
given:
|
||||
XmlSlurper xmlSlurper = ContentUtils.getXmlSlurperWithDefaultErrorHandler()
|
||||
expect:
|
||||
xmlSlurper.getErrorHandler() instanceof DefaultHandler
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user