diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy index 97a3b36aff..a95f9f70cf 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JUnitMethodBodyBuilder.groovy @@ -168,12 +168,12 @@ abstract class JUnitMethodBodyBuilder extends RequestProcessingMethodBodyBuilder @Override protected String getHeaderString(Header header) { - return ".header(${getTestSideValue(header.name)}, ${getTestSideValue(header.serverValue)})" + return ".header(${getTestSideForNonBodyValue(header.name)}, ${getTestSideForNonBodyValue(header.serverValue)})" } @Override protected String getCookieString(Cookie cookie) { - return ".cookie(${getTestSideValue(cookie.key)}, ${getTestSideValue(cookie.serverValue)})" + return ".cookie(${getTestSideForNonBodyValue(cookie.key)}, ${getTestSideForNonBodyValue(cookie.serverValue)})" } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientJUnitMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientJUnitMethodBodyBuilder.groovy index 397eeb021c..5bb567577d 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientJUnitMethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientJUnitMethodBodyBuilder.groovy @@ -154,7 +154,7 @@ class JaxRsClientJUnitMethodBodyBuilder extends JUnitMethodBodyBuilder { if (header.name == 'Content-Type' || header.name == 'Accept') { return } - bb.addLine(".header(\"${header.name}\", \"${header.serverValue}\")") + bb.addLine(".header(\"${header.name}\", ${quotedAndEscaped(header.serverValue as String)})") } } @@ -164,7 +164,7 @@ class JaxRsClientJUnitMethodBodyBuilder extends JUnitMethodBodyBuilder { return } - bb.addLine(".cookie(\"${cookie.key}\", \"${cookie.serverValue}\")") + bb.addLine(".cookie(\"${cookie.key}\", ${quotedAndEscaped(cookie.serverValue as String)})") } } 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 da86a50814..c6ce8480e1 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 @@ -133,6 +133,10 @@ abstract class MethodBodyBuilder implements ClassVerifier { return ContentType.UNKNOWN } + protected String quotedAndEscaped(String string) { + return '"' + StringEscapeUtils.escapeJava(string) + '"' + } + /** * Builds the response body validation code block */ @@ -568,6 +572,19 @@ abstract class MethodBodyBuilder implements ClassVerifier { return '"' + MapConverter.getTestSideValues(object).toString() + '"' } + /** + * Depending on the object type extracts the test side values and + * combines them into a String representation. Unlike the body transformation + * done via {@link MethodBodyBuilder#getTestSideValue(java.lang.Object)} will + * not try to guess the type of the value of the header (e.g. if it's a JSON). + */ + protected String getTestSideForNonBodyValue(Object object) { + if (object instanceof ExecutionProperty) { + return getTestSideValue((ExecutionProperty) object) + } + return quotedAndEscaped(MapConverter.getTestSideValuesForNonBody(object).toString()) + } + /** * Extracts the executable test side values and * @return the code of the executable diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy index 382a31574b..72bc543e1f 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SpockMethodRequestProcessingBodyBuilder.groovy @@ -140,12 +140,12 @@ abstract class SpockMethodRequestProcessingBodyBuilder extends RequestProcessing @Override protected String getHeaderString(Header header) { - return ".header(${getTestSideValue(header.name)}, ${getTestSideValue(header.serverValue)})" + return ".header(${getTestSideForNonBodyValue(header.name)}, ${getTestSideForNonBodyValue(header.serverValue)})" } @Override protected String getCookieString(Cookie cookie) { - return ".cookie(${getTestSideValue(cookie.key)}, ${getTestSideValue(cookie.serverValue)})" + return ".cookie(${getTestSideForNonBodyValue(cookie.key)}, ${getTestSideForNonBodyValue(cookie.serverValue)})" } @Override diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy index e5341240cf..8febf0fc5c 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy @@ -70,12 +70,12 @@ class MapConverter { Closure parsingClosure = JSON_PARSING_CLOSURE) { if (value instanceof String && value) { try { - def json = parsingClosure(value) - if (json instanceof Map) { - return convert(json, closure, parsingClosure) + def parsed = parsingClosure(value) + if (parsed instanceof Map) { + return convert(parsed, closure, parsingClosure) } - else if (json instanceof List) { - return transformValues(json, closure, parsingClosure) + else if (parsed instanceof List) { + return transformValues(parsed, closure, parsingClosure) } } catch (Exception ignore) { @@ -164,4 +164,12 @@ class MapConverter { static Object getTestSideValues(json, Closure parsingClosure = JSON_PARSING_CLOSURE) { return getClientOrServerSideValues(json, TEST_SIDE, parsingClosure) } + + static Object getStubSideValuesForNonBody(object) { + return getClientOrServerSideValues(object, STUB_SIDE, Closure.IDENTITY) + } + + static Object getTestSideValuesForNonBody(object) { + return getClientOrServerSideValues(object, TEST_SIDE, Closure.IDENTITY) + } } 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 28167556dd..cb6ac07113 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 @@ -1320,4 +1320,40 @@ DocumentContext parsedJson = JsonPath.parse(json); "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties, classDataForMethod) } | { String testContent -> assert testContent.contains('''assertThat(response.getHeaderString("Content-Type")).matches("^\\\\s*\\\\S[\\\\S\\\\s]*")''') && testContent.contains('''assertThat(response.getHeaderString("Content-Length")).matches("([1-9]\\\\d*)")'''); return true } } + @Issue("#1034") + def "should not escape headers as jsons [#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + name 'my name' + request { + method POST() + urlPath '/my-url' + headers { + contentType(applicationJson()) + accept(applicationJson()) + header('my-json-header', ''' { "value": "123" } ''') + } + } + response { + status OK() + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + then: + String test = blockBuilder.toString() + SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test) + !test.contains('''[value:123]''') + and: + stubMappingIsValidWireMockStub(contractDsl) + where: + methodBuilderName | methodBuilder + "MockMvcSpockMethodBuilder" | { Contract dsl -> new HttpSpockMethodRequestProcessingBodyBuilder(dsl, properties, classDataForMethod) } + "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties, classDataForMethod) } + "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties, classDataForMethod) } + "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties, classDataForMethod) } + } + }