From 869e644740f05d6aedd9e8b903e8cbd2cf07bbcc Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 9 Jul 2019 17:48:08 +0200 Subject: [PATCH] Fixed resolution of content type when working with WireMock stubs --- .../verifier/converter/ContractsToYaml.groovy | 10 +-- .../verifier/converter/YamlToContracts.groovy | 10 +-- .../WireMockRequestStubStrategy.groovy | 77 ++++++++++--------- .../verifier/file/ContractMetadata.groovy | 26 ++++++- .../verifier/util/ContentUtils.groovy | 10 ++- 5 files changed, 83 insertions(+), 50 deletions(-) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy index 57468993cb..029261164f 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy @@ -41,7 +41,7 @@ import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter import org.springframework.cloud.contract.verifier.util.MapConverter import static org.springframework.cloud.contract.verifier.util.ContentType.XML -import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateContentType +import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType /** * @author Marcin Grzejszczak @@ -72,7 +72,7 @@ class ContractsToYaml { if (!contract.outputMessage) { return } - ContentType contentType = evaluateContentType(contract.response?.headers, + ContentType contentType = evaluateClientSideContentType(contract.response?.headers, contract.response?.body) yamlContract.outputMessage = new YamlContract.OutputMessage() yamlContract.outputMessage.sentTo = MapConverter. @@ -102,7 +102,7 @@ class ContractsToYaml { if (!contract.input) { return } - ContentType contentType = evaluateContentType(contract.input?.messageHeaders, + ContentType contentType = evaluateClientSideContentType(contract.input?.messageHeaders, contract.input?.messageBody) yamlContract.input = new YamlContract.Input() yamlContract.input.assertThat = MapConverter. @@ -132,7 +132,7 @@ class ContractsToYaml { if (!contract.request) { return } - ContentType requestContentType = evaluateContentType(contract.request.headers, + ContentType requestContentType = evaluateClientSideContentType(contract.request.headers, contract.request.body) yamlContract.request = new YamlContract.Request() yamlContract.request.with { YamlContract.Request request -> @@ -336,7 +336,7 @@ class ContractsToYaml { if (!contract.response) { return } - ContentType contentType = evaluateContentType(contract.response?.headers, + ContentType contentType = evaluateClientSideContentType(contract.response?.headers, contract.response?.body) yamlContract.response = new YamlContract.Response() yamlContract.response.with { YamlContract.Response response -> diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy index 247c46bf60..5ac695948d 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy @@ -39,7 +39,7 @@ import org.springframework.util.StringUtils import static java.util.stream.Collectors.toSet import static org.springframework.cloud.contract.verifier.util.ContentType.XML -import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateContentType +import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType /** * @author Marcin Grzejszczak * @author Olga Maciaszek-Sharma @@ -250,7 +250,7 @@ class YamlToContracts { yamlContract.request.matchers?.body?. each { YamlContract.BodyStubMatcher matcher -> ContentType contentType = - evaluateContentType( + evaluateClientSideContentType( yamlHeadersToContractHeaders(yamlContract.request?.headers), yamlContract.request?.body) MatchingTypeValue value = null @@ -367,7 +367,7 @@ class YamlToContracts { yamlContract.response?.matchers?.body?. each { YamlContract.BodyTestMatcher testMatcher -> ContentType contentType = - evaluateContentType( + evaluateClientSideContentType( yamlHeadersToContractHeaders(yamlContract.response?.headers), yamlContract.response?.body) MatchingTypeValue value = null @@ -457,7 +457,7 @@ class YamlToContracts { yamlContract.input.matchers.body?. each { YamlContract.BodyStubMatcher matcher -> ContentType contentType = - evaluateContentType( + evaluateClientSideContentType( yamlHeadersToContractHeaders(yamlContract.input?.messageHeaders), yamlContract.input?.messageBody) MatchingTypeValue value = null @@ -527,7 +527,7 @@ class YamlToContracts { yamlContract.outputMessage?.matchers?.body?. each { YamlContract.BodyTestMatcher testMatcher -> ContentType contentType = - evaluateContentType( + evaluateClientSideContentType( yamlHeadersToContractHeaders(yamlContract.outputMessage?.headers), yamlContract.outputMessage?.body) MatchingTypeValue value = null diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy index e7ec9b332d..71ef494b5a 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy @@ -29,7 +29,6 @@ import groovy.json.JsonOutput import groovy.json.StringEscapeUtils import groovy.transform.PackageScope import groovy.transform.TypeChecked -import groovy.transform.TypeCheckingMode import groovy.util.logging.Commons import org.springframework.cloud.contract.spec.Contract @@ -60,6 +59,7 @@ import static org.springframework.cloud.contract.spec.internal.MatchingType.EQUA import static org.springframework.cloud.contract.spec.internal.MatchingType.NULL import static org.springframework.cloud.contract.spec.internal.MatchingType.TYPE import static org.springframework.cloud.contract.verifier.util.ContentType.FORM +import static org.springframework.cloud.contract.verifier.util.ContentType.JSON import static org.springframework.cloud.contract.verifier.util.ContentUtils.getEqualsTypeFromContentType import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildGStringRegexpForStubSide import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildJSONRegexpMatch @@ -315,38 +315,42 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { } } - @TypeChecked(TypeCheckingMode.SKIP) protected ContentPattern convertToValuePattern(Object object) { switch (object) { - case Pattern: - case RegexProperty: - return WireMock.matching(new RegexProperty(object).pattern()) - case OptionalProperty: - OptionalProperty value = object as OptionalProperty - return WireMock.matching(value.optionalPattern()) - case MatchingStrategy: - MatchingStrategy value = object as MatchingStrategy - switch (value.type) { - case MatchingStrategy.Type.NOT_MATCHING: - return WireMock.notMatching(value.clientValue.toString()) - case MatchingStrategy.Type.ABSENT: - return WireMock.absent() - default: - try { - return WireMock."${value.type.name}"( - clientBody(value.clientValue, contentType)) - } - catch (Throwable t) { - log.error("Exception occurred while trying to call WireMock.${value.type.name}(${value.clientValue})", t) - throw t - } - } + case Pattern: + case RegexProperty: + return WireMock.matching(new RegexProperty(object).pattern()) + case OptionalProperty: + OptionalProperty value = object as OptionalProperty + return WireMock.matching(value.optionalPattern()) + case MatchingStrategy: + MatchingStrategy value = object as MatchingStrategy + switch (value.type) { + case MatchingStrategy.Type.NOT_MATCHING: + return WireMock.notMatching(value.clientValue.toString()) + case MatchingStrategy.Type.ABSENT: + return WireMock.absent() + case MatchingStrategy.Type.EQUAL_TO: + return WireMock.equalTo(clientBody(value.clientValue, contentType).toString()) + case MatchingStrategy.Type.CONTAINS: + return WireMock.containing(clientBody(value.clientValue, contentType).toString()) + case MatchingStrategy.Type.MATCHING: + return WireMock.matching(clientBody(value.clientValue, contentType).toString()) + case MatchingStrategy.Type.EQUAL_TO_JSON: + return WireMock.equalToJson(clientBody(value.clientValue, contentType).toString()) + case MatchingStrategy.Type.EQUAL_TO_XML: + return WireMock.equalToXml(clientBody(value.clientValue, contentType).toString()) + case MatchingStrategy.Type.BINARY_EQUAL_TO: + return WireMock.binaryEqualTo(clientBody(value.clientValue, contentType) as byte[]) default: - return WireMock.equalTo(object.toString()) + throw new UnsupportedOperationException("Unknown matching strategy " + value.type) + } + default: + return WireMock.equalTo(clientBody(object, contentType).toString()) } } - protected static Object clientBody(Object bodyValue, ContentType contentType) { + protected Object clientBody(Object bodyValue, ContentType contentType) { if (FORM == contentType) { if (bodyValue instanceof Map) { // [a:3, b:4] == "a=3&b=4" @@ -365,6 +369,9 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { else if (bodyValue instanceof FromFileProperty) { return bodyValue.isByte() ? bodyValue.asBytes() : bodyValue.asString() } + else if (JSON == contentType) { + return parseBody(bodyValue, contentType) + } return bodyValue } @@ -421,14 +428,14 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { private MatchingStrategy appendBodyRegexpMatchPattern(Object value, ContentType contentType) { switch (contentType) { - case ContentType.JSON: - return new MatchingStrategy( - buildJSONRegexpMatch(value), MatchingStrategy.Type.MATCHING) - case ContentType.UNKNOWN: - return new MatchingStrategy( - buildGStringRegexpForStubSide(value), MatchingStrategy.Type.MATCHING) - case ContentType.XML: - throw new IllegalStateException("XML pattern matching is not implemented yet") + case ContentType.JSON: + return new MatchingStrategy( + buildJSONRegexpMatch(value), MatchingStrategy.Type.MATCHING) + case ContentType.UNKNOWN: + return new MatchingStrategy( + buildGStringRegexpForStubSide(value), MatchingStrategy.Type.MATCHING) + case ContentType.XML: + throw new IllegalStateException("XML pattern matching is not implemented yet") } } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy index d3c55c03d1..7315eae8db 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy @@ -117,19 +117,37 @@ class SingleContractMetadata { DslProperty inputBody = inputBody(currentContract) Headers outputHeaders = outputHeaders(currentContract) DslProperty outputBody = outputBody(currentContract) - this.evaluatedInputTestContentType = ContentUtils.evaluateContentType(inputHeaders, inputBody?.getServerValue()) + this.evaluatedInputTestContentType = tryToEvaluateTestContentType(inputHeaders, inputBody) this.inputTestContentType = inputBody != null ? this.evaluatedInputTestContentType : ContentType.UNKNOWN - this.evaluatedOutputTestContentType = ContentUtils.evaluateContentType(outputHeaders, outputBody?.getServerValue()) + this.evaluatedOutputTestContentType = tryToEvaluateTestContentType(outputHeaders, outputBody) this.outputTestContentType = outputBody != null ? this.evaluatedOutputTestContentType : ContentType.UNKNOWN - this.evaluatedInputStubContentType = ContentUtils.evaluateContentType(inputHeaders, inputBody?.getClientValue()) + this.evaluatedInputStubContentType = tryToEvaluateStubContentType(inputHeaders, inputBody) this.inputStubContentType = inputBody != null ? this.evaluatedInputStubContentType : ContentType.UNKNOWN - this.evaluatedOutputStubContentType = ContentUtils.evaluateContentType(outputHeaders, outputBody?.getClientValue()) + this.evaluatedOutputStubContentType = tryToEvaluateStubContentType(outputHeaders, outputBody) this.outputStubContentType = outputBody != null ? this.evaluatedOutputStubContentType : ContentType.UNKNOWN this.http = currentContract.request != null this.contractMetadata = contractMetadata this.stubsFile = contractMetadata.getPath() != null ? contractMetadata.getPath().toFile() : null } + private ContentType tryToEvaluateStubContentType(Headers mainHeaders, DslProperty body) { + ContentType contentType = ContentUtils.evaluateClientSideContentType(mainHeaders, body?.getClientValue()) + if (contentType == ContentType.DEFINED || contentType == ContentType.UNKNOWN) { + // try to retrieve from the other side (e.g. stub side was a regex, but test side is concrete) + return ContentUtils.evaluateServerSideContentType(mainHeaders, body?.getServerValue()) + } + return contentType + } + + private ContentType tryToEvaluateTestContentType(Headers mainHeaders, DslProperty body) { + ContentType contentType = ContentUtils.evaluateClientSideContentType(mainHeaders, body?.getServerValue()) + if (contentType == ContentType.DEFINED || contentType == ContentType.UNKNOWN) { + // try to retrieve from the other side (e.g. stub side was a regex, but test side is concrete) + return ContentUtils.evaluateServerSideContentType(mainHeaders, body?.getClientValue()) + } + return contentType + } + boolean isJson() { return this.inputTestContentType == ContentType.JSON || this.outputTestContentType == ContentType.JSON || diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy index 293946ab6d..91a527cb5d 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy @@ -650,7 +650,7 @@ class ContentUtils { escapeJava(property.value.serverValue.toString()) + quote + ".getBytes()" } - static ContentType evaluateContentType(Headers contractHeaders, Object body) { + static ContentType evaluateClientSideContentType(Headers contractHeaders, Object body) { ContentType contentType = recognizeContentTypeFromHeader(contractHeaders) if (UNKNOWN == contentType) { contentType = recognizeContentTypeFromContent(body) @@ -658,6 +658,14 @@ class ContentUtils { return contentType } + static ContentType evaluateServerSideContentType(Headers contractHeaders, Object body) { + ContentType contentType = recognizeContentTypeFromTestHeader(contractHeaders) + if (UNKNOWN == contentType) { + contentType = recognizeContentTypeFromContent(body) + } + return contentType + } + /** * Creates new {@link XmlSlurper} with default error handler. *