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)}]
+ }
}