From 2fc413755b46284315bd544619d0c429e7773c29 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 9 Mar 2018 16:56:47 +0100 Subject: [PATCH] Fixed unnecessary quoting of non string value when referencing request from response fixes gh-559 --- spring-cloud-contract-verifier/pom.xml | 4 ++ .../verifier/builder/MethodBodyBuilder.groovy | 58 ++++++++++++++++--- .../builder/MethodBodyBuilderSpec.groovy | 41 +++++++++++++ 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/spring-cloud-contract-verifier/pom.xml b/spring-cloud-contract-verifier/pom.xml index 1d92556c18..0a2befa3a7 100644 --- a/spring-cloud-contract-verifier/pom.xml +++ b/spring-cloud-contract-verifier/pom.xml @@ -85,6 +85,10 @@ com.github.jknack handlebars + + commons-beanutils + commons-beanutils + org.yaml snakeyaml diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy index 8b5e4bc016..96e26c1f5f 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy @@ -16,15 +16,17 @@ package org.springframework.cloud.contract.verifier.builder +import java.util.regex.Pattern + import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.PathNotFoundException import groovy.json.JsonOutput import groovy.transform.PackageScope import groovy.transform.TypeChecked +import org.apache.commons.beanutils.PropertyUtilsBean import org.apache.commons.text.StringEscapeUtils -import org.apache.commons.logging.Log -import org.apache.commons.logging.LogFactory + import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.ContractTemplate import org.springframework.cloud.contract.spec.internal.BodyMatcher @@ -47,8 +49,6 @@ import org.springframework.cloud.contract.verifier.util.MapConverter import org.springframework.util.SerializationUtils import org.springframework.util.StringUtils -import java.util.regex.Pattern - import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue /** * Main class for building method body. @@ -64,6 +64,9 @@ import static org.springframework.cloud.contract.verifier.util.ContentUtils.extr abstract class MethodBodyBuilder { private static final Closure GET_SERVER_VALUE = { it instanceof DslProperty ? it.serverValue : it } + private static final String FROM_REQUEST_PREFIX = "request." + private static final String FROM_REQUEST_BODY = "body" + private static final String FROM_REQUEST_PATH = "path" protected final ContractVerifierConfigProperties configProperties protected final TemplateProcessor templateProcessor @@ -332,11 +335,15 @@ abstract class MethodBodyBuilder { processText(bb, "", convertedResponseBody) addColonIfRequired(bb) } - + private void addJsonResponseBodyCheck(BlockBuilder bb, convertedResponseBody, BodyMatchers bodyMatchers) { appendJsonPath(bb, getResponseAsString()) Object copiedBody = cloneBody(convertedResponseBody) convertedResponseBody = JsonToJsonPathsConverter.removeMatchingJsonPaths(convertedResponseBody, bodyMatchers) + // remove quotes from fromRequest objects before picking json paths + TestSideRequestTemplateModel templateModel = contract.request?.body ? + TestSideRequestTemplateModel.from(contract.request) : null + convertedResponseBody = MapConverter.transformValues(convertedResponseBody, returnReferencedEntries(templateModel)) JsonPaths jsonPaths = new JsonToJsonPathsConverter(configProperties).transformToJsonPathWithTestsSideValues(convertedResponseBody) DocumentContext parsedRequestBody if (contract.request?.body) { @@ -351,6 +358,14 @@ abstract class MethodBodyBuilder { bb.addLine("assertThatJson(parsedJson)" + postProcessedMethod) addColonIfRequired(bb) } + doBodyMatchingIfPresent(bodyMatchers, bb, copiedBody) + if (!(convertedResponseBody instanceof Map || convertedResponseBody instanceof List)) { + simpleTextResponseBodyCheck(bb, convertedResponseBody) + } + processBodyElement(bb, "", "", convertedResponseBody) + } + + private void doBodyMatchingIfPresent(BodyMatchers bodyMatchers, BlockBuilder bb, copiedBody) { if (bodyMatchers?.hasMatchers()) { bb.endBlock() bb.addLine(addCommentSignIfRequired('and:')) @@ -366,10 +381,35 @@ abstract class MethodBodyBuilder { } } } - if (!(convertedResponseBody instanceof Map || convertedResponseBody instanceof List)) { - simpleTextResponseBodyCheck(bb, convertedResponseBody) + } + + private Closure returnReferencedEntries(TestSideRequestTemplateModel templateModel) { + return { entry -> + if (!(entry instanceof String) || !templateModel) { + return entry + } + String entryAsString = (String) entry + if (templateProcessor.containsTemplateEntry(entryAsString) && + !templateProcessor.containsJsonPathTemplateEntry(entryAsString)) { + String justEntry = entryAsString - contractTemplate.openingTemplate() - + contractTemplate.closingTemplate() - FROM_REQUEST_PREFIX + if (justEntry == FROM_REQUEST_BODY) { + // the body should be transformed by standard mechanism + return entry + } + try { + Object result = new PropertyUtilsBean().getProperty(templateModel, justEntry) + // Path from the Test model is an object and we'd like to return its String representation + if (justEntry == FROM_REQUEST_PATH) { + return result.toString() + } + return result + } catch (Exception e) { + return entry + } + } + return entry } - processBodyElement(bb, "", "", convertedResponseBody) } protected String processIfTemplateIsPresent(String method, DocumentContext parsedRequestBody) { @@ -383,6 +423,8 @@ abstract class MethodBodyBuilder { .replace('"' + contractTemplate.openingTemplate(), contractTemplate.openingTemplate()) .replace(contractTemplate.closingTemplate() + '"', contractTemplate.closingTemplate()) } + } else if (templateProcessor.containsTemplateEntry(method)) { + } return method } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy index 37d9a2038b..7c8d31a9de 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy @@ -380,6 +380,47 @@ DocumentContext parsedJson = JsonPath.parse(json); "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | { String string -> string.contains('assertThat(responseBody).isEqualTo("My name");') } } + @Issue("#559") + def "should reference request from body without escaping of non-string [#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + request { + method 'GET' + url( '/mytest') { + queryParameters { + parameter("foo", "bar") + parameter("number", 1) + } + } + body("""{ "name": "My name" }""") + } + response { + status 200 + body ( + foo: fromRequest().query("foo"), + number: fromRequest().query("number") + ) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + then: + String test = blockBuilder.toString() + SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test) + test.contains('''assertThatJson(parsedJson).field("['foo']").isEqualTo("bar")''') + test.contains('''assertThatJson(parsedJson).field("['number']").isEqualTo(1)''') + 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) } + } + @Issue("#465") def "should work for '/' url for [#methodBuilderName]"() { given: