From c067774eee8d1b517682a84faf4a8b69c7abcb43 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 7 Dec 2016 16:29:11 +0100 Subject: [PATCH] Fixed the invalid regex escape fixes #162 --- README.adoc | 2 +- .../contract/spec/internal/Headers.groovy | 6 +++ .../spec/internal/NotToEscapePattern.groovy | 21 ++++++++++ .../contract/spec/internal/Response.groovy | 2 +- .../builder/JUnitMethodBodyBuilder.groovy | 4 ++ .../MockMvcJUnitMethodBodyBuilder.groovy | 8 ++++ ...kMethodRequestProcessingBodyBuilder.groovy | 7 ++++ ...kMethodRequestProcessingBodyBuilder.groovy | 2 +- .../MockMvcMethodBodyBuilderSpec.groovy | 38 +++++++++++++++++-- 9 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy diff --git a/README.adoc b/README.adoc index 8bab5e7f92..c9fcc0cf37 100644 --- a/README.adoc +++ b/README.adoc @@ -1311,7 +1311,7 @@ Example of a `pom.xml` inside the `server` folder. UTF-8 1.8 - 1.0.3.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT Camden.BUILD-SNAPSHOT diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy index cb8ab772b8..c54a14615f 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Headers.groovy @@ -20,6 +20,8 @@ import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import groovy.transform.TypeChecked +import java.util.regex.Pattern + /** * Represents a set of headers of a request / response or a message * @@ -68,6 +70,10 @@ class Headers { return new DslProperty(value) } + protected NotToEscapePattern notEscaped(Pattern pattern) { + return new NotToEscapePattern(pattern) + } + /** * Converts the headers into their stub side representations and returns as * a map of String key => Object value. diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy new file mode 100644 index 0000000000..b72559bb0d --- /dev/null +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NotToEscapePattern.groovy @@ -0,0 +1,21 @@ +package org.springframework.cloud.contract.spec.internal + +import java.util.regex.Pattern + + +/** + * Special case of Patterns that we don't want to escape + * + * @author Marcin Grzejszczak + * @since 1.0.3 + */ +class NotToEscapePattern extends DslProperty { + + NotToEscapePattern(Pattern clientValue, Pattern serverValue) { + super(clientValue, serverValue) + } + + NotToEscapePattern(Pattern singleValue) { + super(singleValue) + } +} diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy index f2d1846a98..8e9acdd792 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy @@ -144,7 +144,7 @@ class Response extends Common { @Override DslProperty matching(String value) { - return $(p(Pattern.compile("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*")), + return $(p(notEscaped(Pattern.compile("${RegexpUtils.escapeSpecialRegexWithSingleEscape(value)}.*"))), c(value)) } } 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 f9c5ca2b6c..42e6916aba 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 @@ -169,6 +169,10 @@ abstract class JUnitMethodBodyBuilder extends RequestProcessingMethodBodyBuilder private String buildEscapedMatchesMethod(Pattern headerValue) { String escapedHeader = convertUnicodeEscapesIfRequired("$headerValue") + return createMatchesMethod(escapedHeader) + } + + protected String createMatchesMethod(String escapedHeader) { return "matches(\"$escapedHeader\")" } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcJUnitMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcJUnitMethodBodyBuilder.groovy index 622c6bde79..5357f95e46 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcJUnitMethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcJUnitMethodBodyBuilder.groovy @@ -21,6 +21,7 @@ import groovy.transform.TypeChecked import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.internal.Header import org.springframework.cloud.contract.spec.internal.ExecutionProperty +import org.springframework.cloud.contract.spec.internal.NotToEscapePattern import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import java.util.regex.Pattern @@ -62,6 +63,13 @@ class MockMvcJUnitMethodBodyBuilder extends JUnitMethodBodyBuilder { return null } + @Override + protected void processHeaderElement(BlockBuilder blockBuilder, String property, Object value) { + if (value instanceof NotToEscapePattern) { + blockBuilder.addLine("assertThat(response.header(\"$property\")).${createMatchesMethod(value.serverValue.pattern())};") + } + } + @Override protected void processHeaderElement(BlockBuilder blockBuilder, String property, String value) { blockBuilder.addLine("assertThat(response.header(\"$property\")).${createHeaderComparison(value)}") diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcSpockMethodRequestProcessingBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcSpockMethodRequestProcessingBodyBuilder.groovy index a1655e52ce..ff0dcba879 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcSpockMethodRequestProcessingBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcSpockMethodRequestProcessingBodyBuilder.groovy @@ -21,6 +21,7 @@ import groovy.transform.TypeChecked import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.internal.ExecutionProperty import org.springframework.cloud.contract.spec.internal.Header +import org.springframework.cloud.contract.spec.internal.NotToEscapePattern import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import java.util.regex.Pattern @@ -55,6 +56,12 @@ class MockMvcSpockMethodRequestProcessingBodyBuilder extends SpockMethodRequestP return 'response.body.asString()' } + @Override + protected void processHeaderElement(BlockBuilder blockBuilder, String property, Object value) { + if (value instanceof NotToEscapePattern) { + blockBuilder.addLine("response.header('$property') ${patternComparison(value.serverValue)}") + } + } @Override protected void processHeaderElement(BlockBuilder blockBuilder, String property, ExecutionProperty exec) { blockBuilder.addLine("${exec.insertValue("response.header(\'$property\')")}") 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 cdbe20c78a..3302b1b5d2 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 @@ -147,7 +147,7 @@ abstract class SpockMethodRequestProcessingBodyBuilder extends RequestProcessing return patternComparison(headerValue) } - private String patternComparison(Pattern pattern) { + protected String patternComparison(Pattern pattern) { return "==~ java.util.regex.Pattern.compile('$pattern')" } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy index 9f27a1a285..64a5501cec 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy @@ -22,6 +22,7 @@ import org.springframework.cloud.contract.verifier.dsl.WireMockStubVerifier import spock.lang.Issue import spock.lang.Shared import spock.lang.Specification +import spock.lang.Unroll import spock.util.environment.RestoreSystemProperties import java.util.regex.Pattern @@ -1537,7 +1538,7 @@ World.'''""" responseElement: $(producer(regex('[0-9]{7}'))) ]) headers { - contentType("application/vnd.fraud.v1.json") + contentType("application/vnd.fraud.v1+json") } } } @@ -1557,8 +1558,8 @@ World.'''""" strippedTest.matches(""".*header\\("header", "application\\/vnd\\.fraud\\.v1\\+json;.*"\\).*""") strippedTest.matches(""".*body\\('''\\{"requestElement":"[0-9]{5}"\\}'''\\).*""") strippedTest.matches(""".*put\\("/foo/[0-9]{5}"\\).*""") - strippedTest.contains("""response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/vnd\\.fraud\\.v1\\.json.*')""") - "application/vnd.fraud.v1+json;charset=UTF-8".matches('application/vnd.fraud.v1.json.*') + strippedTest.contains("""response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/vnd\\.fraud\\.v1\\+json.*')""") + "application/vnd.fraud.v1+json;charset=UTF-8".matches('application/vnd\\.fraud\\.v1\\+json.*') strippedTest.contains("""assertThatJson(parsedJson).field("responseElement").matches("[0-9]{7}")""") } @@ -1749,4 +1750,35 @@ World.'''""" test.contains('assertThatJson(parsedJson).field("uuid").matches("[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}")') !test.contains('cursor') } + + @Issue('#162') + @Unroll + def "should escape regex properly for content type"() { + given: + Contract contractDsl = Contract.make { + request { + method GET() + url 'get' + headers { + contentType("application/vnd.fraud.v1+json") + } + } + response { + status 200 + headers { + contentType("application/vnd.fraud.v1+json") + } + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + def test = blockBuilder.toString() + then: + test.contains('application/vnd\\.fraud\\.v1\\+json.*') + where: + methodBuilder << [{ Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties)}, + { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties)}] + } }