diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy index ff33fcd4d0..b8fca2e9c6 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Common.groovy @@ -233,7 +233,7 @@ class Common { } void assertThatSidesMatch(OptionalProperty stubSide, Object testSide) { - assert testSide ==~ Pattern.compile(stubSide.optionalPattern()) + assert testSide == null || testSide ==~ Pattern.compile(stubSide.optionalPattern()) } void assertThatSidesMatch(Pattern pattern, String value) { @@ -245,7 +245,7 @@ class Common { } void assertThatSidesMatch(MatchingStrategy firstSide, MatchingStrategy secondSide) { - if (firstSide.type == MatchingStrategy.Type.ABSENT && secondSide != MatchingStrategy.Type.ABSENT) { + if (firstSide.type == MatchingStrategy.Type.ABSENT && secondSide.type != MatchingStrategy.Type.ABSENT) { throwAbsentError() } } diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy index 4c36c70029..de2506e850 100644 --- a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy @@ -398,4 +398,28 @@ then: assertThat(contract.request.bodyMatchers.hasMatchers()).isTrue() assertThat(contract.response.bodyMatchers.hasMatchers()).isTrue() } + + def 'should work with optional and null value of a field'() { + given: + def contract = Contract.make { + description("Creating user") + name("Create user") + request { + method 'POST' + url '/api/user' + body( + address: $(consumer(optional(regex(alphaNumeric()))), producer(null)), + name: $(consumer(optional(regex(alphaNumeric()))), producer('')) + ) + headers { + contentType(applicationJson()) + } + } + response { + status 201 + } + } + expect: + contract != null + } } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy index aaa19436ad..b73707243c 100755 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy @@ -2713,6 +2713,47 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie } + @Issue("#1257") + def "should work with null request element on the client side and optional stub entry"() { + given: + Contract contractDsl = Contract.make { + description("Creating user") + name("Create user") + request { + method 'POST' + url '/api/user' + body( + address: $(consumer(optional(regex(alphaNumeric()))), producer(null)), + name: $(consumer(optional(regex(alphaNumeric()))), producer('')) + ) + headers { + contentType(applicationJson()) + } + } + response { + status 201 + } + } + and: + String wireMockStub = new WireMockStubStrategy("Test", + new ContractMetadata(null, false, 0, null, contractDsl), contractDsl) + .toWireMockClientStub() + + and: + int port = SocketUtils.findAvailableTcpPort() + WireMockServer server = new WireMockServer(config().port(port)) + server.start() + and: + stubMappingIsValidWireMockStub(wireMockStub) + server.addStubMapping(WireMockStubMapping.buildFrom(wireMockStub)) + when: + ResponseEntity entity = callWithOptionalAndEmpty(port) + then: + entity.statusCodeValue == 201 + cleanup: + server?.shutdown() + } + WireMockConfiguration config() { return new WireMockConfiguration().extensions(responseTemplateTransformer()) } @@ -2732,6 +2773,13 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie .body("{\"foo\":\"bar\",\"baz\":5}"), String.class) } + ResponseEntity callWithOptionalAndEmpty(int port) { + return new TestRestTemplate().exchange( + RequestEntity.post(URI.create("http://localhost:" + port + "/api/user")) + .header("Content-Type", "application/json") + .body("{\"foo\":null,\"name\":\"\"}"), String.class) + } + ResponseEntity callBytes(int port, File request) { return new TestRestTemplate().exchange( RequestEntity.put(URI.create("http://localhost:" + port + "/1"))