Ensured that stub part of the body gets returned form WIreMock response, fixes gh-611

This commit is contained in:
Marcin Grzejszczak
2018-04-12 15:49:18 +02:00
parent 7de5c7c4dd
commit d467341427
8 changed files with 148 additions and 59 deletions

View File

@@ -33,7 +33,7 @@ import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.ContentUtils
import org.springframework.cloud.contract.verifier.util.MapConverter
import static ContentUtils.extractValue
import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue
import static org.springframework.cloud.contract.verifier.util.MapConverter.transformValues
/**
* Common abstraction over WireMock Request / Response conversion implementations
@@ -53,12 +53,12 @@ abstract class BaseWireMockStubStrategy {
protected final Contract contract
protected BaseWireMockStubStrategy(Contract contract) {
this.processor = processor()
this.processor = templateProcessor()
this.template = contractTemplate()
this.contract = contract
}
private TemplateProcessor processor() {
private TemplateProcessor templateProcessor() {
return new HandlebarsTemplateProcessor()
}
@@ -145,7 +145,7 @@ abstract class BaseWireMockStubStrategy {
* For the given {@link ContentType} returns the String version of the body
*/
String parseBody(GString value, ContentType contentType) {
Object processedValue = extractValue(value, contentType, { Object o -> o instanceof DslProperty ? o.clientValue : o })
Object processedValue = extractValue(value, contentType, { Object o -> o instanceof DslProperty ? o.clientValue : o })
if (processedValue instanceof GString) {
return parseBody(processedValue.toString(), contentType)
}

View File

@@ -28,6 +28,7 @@ import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.spec.internal.Response
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.ContentUtils
import org.springframework.cloud.contract.verifier.util.MapConverter
import org.springframework.core.io.support.SpringFactoriesLoader
@@ -84,7 +85,7 @@ class WireMockResponseStubStrategy extends BaseWireMockStubStrategy {
private void appendBody(ResponseDefinitionBuilder builder) {
if (response.body) {
Object body = response.body.clientValue
Object body = MapConverter.getStubSideValues(response.body)
ContentType contentType = recognizeContentTypeFromHeader(response.headers)
if (contentType == ContentType.UNKNOWN) {
contentType = recognizeContentTypeFromContent(body)

View File

@@ -21,6 +21,7 @@ import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.transform.TypeChecked
import groovy.util.logging.Slf4j
import org.apache.commons.lang3.StringEscapeUtils
import org.codehaus.groovy.runtime.GStringImpl
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
@@ -35,6 +36,7 @@ import java.util.regex.Pattern
import static org.apache.commons.text.StringEscapeUtils.escapeJava
import static org.apache.commons.text.StringEscapeUtils.escapeJson
import static org.apache.commons.text.StringEscapeUtils.escapeXml11
import static org.apache.commons.text.StringEscapeUtils.unescapeXml
/**
* A utility class that can operate on a message body basing on the provided Content Type.
@@ -208,7 +210,7 @@ class ContentUtils {
}
private static String transformXMLStringValue(Object obj, Closure valueProvider) {
return escapeXml11(obj.toString())
return escapeXml11(unescapeXml(obj.toString()))
}
private static String transformXMLStringValue(DslProperty dslProperty, Closure valueProvider) {

View File

@@ -340,23 +340,13 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
return true;
if (o == null || getClass() != o.getClass())
return false;
DelegatingJsonVerifiable that = (DelegatingJsonVerifiable) o;
if (this.delegate != null ? !this.delegate.equals(that.delegate) : that.delegate != null)
return false;
if (this.delegate == null) {
return false;
}
if (this.delegate.jsonPath() == null && that.delegate.jsonPath() == null)
return true;
return this.delegate.jsonPath().equals(that.delegate.jsonPath());
return this.methodsBuffer.toString().equals(that.methodsBuffer.toString());
}
@Override
public int hashCode() {
int result = this.delegate != null ? this.delegate.jsonPath().hashCode() : 0;
int result = this.methodsBuffer.toString().hashCode();
return 31 * result;
}

View File

@@ -274,7 +274,7 @@ class JsonToJsonPathsConverter {
String systemPropValue = System.getProperty(SIZE_ASSERTION_SYSTEM_PROP)
Boolean configPropValue = configProperties.assertJsonSize
if ((systemPropValue != null && Boolean.parseBoolean(systemPropValue)) ||
configPropValue) {
configPropValue && listContainsOnlyPrimitives(value)) {
addArraySizeCheck(key, value, closure)
} else {
if (log.isDebugEnabled()) {
@@ -323,6 +323,10 @@ class JsonToJsonPathsConverter {
return jsonPathVerifiable.matches((object as Pattern).pattern())
}
return jsonPathVerifiable.contains(object)
} else if (element instanceof List) {
if (listContainsOnlyPrimitives(element)) {
return jsonPathVerifiable.array()
}
}
return jsonPathVerifiable
}
@@ -367,6 +371,7 @@ class JsonToJsonPathsConverter {
}
}
}
private boolean isAnEntryWithLists(def value) {
if (!(value instanceof Iterable)) {
return false

View File

@@ -1597,10 +1597,10 @@ World.'''"""
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Programming").value()')
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Java").value()')
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Spring").value()')
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Boot").value()')
test.contains('assertThatJson(parsedJson).array().array().arrayField().isEqualTo("Programming").value()')
test.contains('assertThatJson(parsedJson).array().array().arrayField().isEqualTo("Java").value()')
test.contains('assertThatJson(parsedJson).array().array().arrayField().isEqualTo("Spring").value()')
test.contains('assertThatJson(parsedJson).array().array().arrayField().isEqualTo("Boot").value()')
and:
SyntaxChecker.tryToCompile(methodBuilderName, blockBuilder.toString())
where:

View File

@@ -20,6 +20,7 @@ import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer
import groovy.json.JsonBuilder
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.cloud.contract.spec.Contract
@@ -1842,6 +1843,76 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
server?.shutdown()
}
@Issue('#266')
def "should work with array of arrays"() {
given:
org.springframework.cloud.contract.spec.Contract groovyDsl = Contract.make {
request {
method 'POST'
urlPath '/api/categories'
body([["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]])
headers {
header('Content-Type': 'application/json;charset=UTF-8')
}
}
response {
status 200
body([["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]])
headers {
header('Content-Type': 'application/json;charset=UTF-8')
}
}
}
when:
def json = toWireMockClientJsonStub(groovyDsl)
then:
AssertionUtil.assertThatJsonsAreEqual(('''
{
"request" : {
"urlPath" : "/api/categories",
"method" : "POST",
"headers" : {
"Content-Type" : {
"equalTo" : "application/json;charset=UTF-8"
}
},
"bodyPatterns" : [ {
"matchesJsonPath" : "$[*][?(@ == 'Spring')]"
}, {
"matchesJsonPath" : "$[*][?(@ == 'Boot')]"
}, {
"matchesJsonPath" : "$[*][?(@ == 'Programming')]"
}, {
"matchesJsonPath" : "$[*][?(@ == 'Java')]"
} ]
},
"response" : {
"status" : 200,
"body" : "[\\"[\\\\\\"Programming\\\\\\",\\\\\\"Java\\\\\\"]\\",\\"[\\\\\\"Programming\\\\\\",\\\\\\"Java\\\\\\",\\\\\\"Spring\\\\\\",\\\\\\"Boot\\\\\\"]\\"]",
"headers" : {
"Content-Type" : "application/json;charset=UTF-8"
},
"transformers" : [ "response-template", "foo-transformer" ]
}
}
'''), json)
and:
stubMappingIsValidWireMockStub(json)
and:
int port = SocketUtils.findAvailableTcpPort()
WireMockServer server = new WireMockServer(config().port(port))
server.start()
server.addStubMapping(WireMockStubMapping.buildFrom(json))
then:
String entity = callApiCategories(port)
and:
AssertionUtil.assertThatJsonsAreEqual(('''
["[\\"Programming\\",\\"Java\\"]","[\\"Programming\\",\\"Java\\",\\"Spring\\",\\"Boot\\"]"]
'''), entity)
cleanup:
server?.shutdown()
}
@Issue('#269')
def "should create a stub for dot separated keys"() {
given:
@@ -2028,4 +2099,11 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
.header("Cookie", "foo=bar")
.body("{\"foo\":\"bar\",\"baz\":5}"), String.class)
}
String callApiCategories(int port) {
return new TestRestTemplate().exchange(
RequestEntity.post(URI.create("http://localhost:" + port + "/api/categories"))
.header("Content-Type", "application/json;charset=UTF-8")
.body(JsonOutput.toJson([["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]])), String.class).body
}
}

View File

@@ -673,20 +673,25 @@ class JsonToJsonPathsConverterSpec extends Specification {
when:
JsonPaths pathAndValues = new JsonToJsonPathsConverter().transformToJsonPathWithTestsSideValues(new JsonSlurper().parseText(json))
then:
DocumentContext context = JsonPath.parse(json)
pathAndValues.each {
assert context.read(it.jsonPath(), JSONArray)
}
and:
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(38.995548)""" &&
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(38.995548)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == 38.995548)]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(-77.119759)""" &&
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(-77.119759)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == -77.119759)]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(-76.909393)""" &&
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(-76.909393)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == -76.909393)]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(38.791645)""" &&
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(38.791645)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == 38.791645)]"""
}
and:
@@ -700,8 +705,8 @@ class JsonToJsonPathsConverterSpec extends Specification {
@RestoreSystemProperties
def "should manage to parse a double array with array size check"() {
given:
System.setProperty('spring.cloud.contract.verifier.assert.size', 'true')
String json = '''
System.setProperty('spring.cloud.contract.verifier.assert.size', 'true')
String json = '''
[{
"place":
{
@@ -717,38 +722,46 @@ class JsonToJsonPathsConverterSpec extends Specification {
}]
'''
when:
JsonPaths pathAndValues = new JsonToJsonPathsConverter().transformToJsonPathWithTestsSideValues(new JsonSlurper().parseText(json))
JsonPaths pathAndValues = new JsonToJsonPathsConverter().transformToJsonPathWithTestsSideValues(new JsonSlurper().parseText(json))
then:
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(38.995548)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == 38.995548)]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(-77.119759)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == -77.119759)]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(-76.909393)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == -76.909393)]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().arrayField().isEqualTo(38.791645)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == 38.791645)]"""
}
pathAndValues.find {
it.method()== """.hasSize(1)""" &&
it.jsonPath() == """\$"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().hasSize(2)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*]"""
}
pathAndValues.find {
it.method()== """.array().field("['place']").field("['bounding_box']").array("['coordinates']").hasSize(1)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*]"""
}
DocumentContext context = JsonPath.parse(json)
pathAndValues.each {
assert context.read(it.jsonPath(), JSONArray)
}
pathAndValues.find {
it.method() == """.hasSize(1)""" &&
it.jsonPath() == """\$"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(-77.119759)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == -77.119759)]"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(38.995548)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == 38.995548)]"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").hasSize(1)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*]"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().hasSize(2)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*]"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(38.791645)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == 38.791645)]"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().hasSize(2)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*]"""
}
pathAndValues.find {
it.method() == """.array().field("['place']").field("['bounding_box']").array("['coordinates']").array().array().arrayField().isEqualTo(-76.909393)""" &&
it.jsonPath() == """\$[*].['place'].['bounding_box'].['coordinates'][*][*][?(@ == -76.909393)]"""
}
and:
pathAndValues.size() == 7
pathAndValues.size() == 8
and:
pathAndValues.each {
JsonAssertion.assertThat(json).matchesJsonPath(it.jsonPath())