From 3f90880cbb2d6db631f24b63b5d2b5b7b40ec164 Mon Sep 17 00:00:00 2001 From: Anatolii Zhmaiev Date: Wed, 26 Aug 2020 14:43:53 +0300 Subject: [PATCH] Convert from groovy to java (#1482) --- .../WireMockResponseStubStrategy.groovy | 117 ------- .../dsl/wiremock/WireMockStubStrategy.groovy | 85 ----- .../verifier/file/ContractMetadata.groovy | 260 --------------- .../WireMockResponseStubStrategy.java | 133 ++++++++ .../dsl/wiremock/WireMockStubStrategy.java | 93 ++++++ ...tcherToWireMockValuePatternConverter.java} | 30 +- .../verifier/file/ContractMetadata.java | 122 +++++++ .../verifier/file/SingleContractMetadata.java | 315 ++++++++++++++++++ 8 files changed, 677 insertions(+), 478 deletions(-) delete mode 100755 spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy delete mode 100644 spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy delete mode 100644 spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy create mode 100755 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.java rename spring-cloud-contract-verifier/src/main/{groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.groovy => java/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.java} (60%) create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/ContractMetadata.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/SingleContractMetadata.java diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy deleted file mode 100755 index 1ad25b64ba..0000000000 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.dsl.wiremock - -import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder -import com.github.tomakehurst.wiremock.extension.Extension -import com.github.tomakehurst.wiremock.http.HttpHeader -import com.github.tomakehurst.wiremock.http.HttpHeaders -import com.github.tomakehurst.wiremock.http.ResponseDefinition -import groovy.transform.PackageScope -import groovy.transform.TypeChecked - -import org.springframework.cloud.contract.spec.Contract -import org.springframework.cloud.contract.spec.internal.FromFileProperty -import org.springframework.cloud.contract.spec.internal.Request -import org.springframework.cloud.contract.spec.internal.Response -import org.springframework.cloud.contract.verifier.file.SingleContractMetadata -import org.springframework.cloud.contract.verifier.util.ContentType -import org.springframework.cloud.contract.verifier.util.MapConverter -import org.springframework.core.io.support.SpringFactoriesLoader -/** - * Converts a {@link Request} into {@link ResponseDefinition} - * - * @author Marcin Grzejszczak - * @author Olga Maciaszek-Sharma - * - * @since 1.0.0 - */ -@TypeChecked -@PackageScope -class WireMockResponseStubStrategy extends BaseWireMockStubStrategy { - - private final Response response - private final ContentType contentType - - WireMockResponseStubStrategy(Contract groovyDsl, SingleContractMetadata singleContractMetadata) { - super(groovyDsl) - this.response = groovyDsl.response - this.contentType = contentType(singleContractMetadata) - } - - protected ContentType contentType(SingleContractMetadata singleContractMetadata) { - return singleContractMetadata.evaluatedOutputStubContentType - } - - @PackageScope - ResponseDefinition buildClientResponseContent() { - if (!response) { - return null - } - ResponseDefinitionBuilder builder = new ResponseDefinitionBuilder() - .withStatus(MapConverter.getStubSideValues(response.status) as Integer) - appendHeaders(builder) - appendBody(builder) - appendResponseDelayTime(builder) - builder.withTransformers(responseTransformerNames()) - return builder.build() - } - - private String[] responseTransformerNames() { - List wireMockExtensions = SpringFactoriesLoader. - loadFactories(WireMockExtensions, null) - if (wireMockExtensions) { - return ((List) wireMockExtensions - .collect { WireMockExtensions extension -> extension.extensions() } - .flatten()) - .collect { Extension e -> e.getName() } as String[] - } - return [new DefaultResponseTransformer().getName()] as String[] - } - - private void appendHeaders(ResponseDefinitionBuilder builder) { - if (response.headers) { - builder.withHeaders(new HttpHeaders(response.headers.entries?.collect { - new HttpHeader(it.name, MapConverter.getStubSideValues(it.clientValue). - toString()) - })) - } - } - - private void appendBody(ResponseDefinitionBuilder builder) { - if (response.body) { - Object body = MapConverter.getStubSideValues(response.body) - if (body instanceof byte[]) { - builder.withBody(body) - } - else if (body instanceof FromFileProperty && body.isByte()) { - builder.withBody(body.asBytes()) - } - else { - builder.withBody(parseBody(body, contentType)) - } - } - } - - private void appendResponseDelayTime(ResponseDefinitionBuilder builder) { - // TODO: Add a missing test for this - if (response.delay) { - builder.withFixedDelay(response.delay.clientValue as Integer) - } - } - -} diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy deleted file mode 100644 index 1a3228fd8c..0000000000 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.groovy +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.dsl.wiremock - -import com.github.tomakehurst.wiremock.http.ResponseDefinition -import com.github.tomakehurst.wiremock.matching.RequestPattern -import com.github.tomakehurst.wiremock.stubbing.StubMapping - -import org.springframework.cloud.contract.spec.Contract -import org.springframework.cloud.contract.verifier.file.ContractMetadata -import org.springframework.cloud.contract.verifier.file.SingleContractMetadata - -/** - * Converts a {@link ContractMetadata} into a WireMock stub - * - * @since 1.0.0 - */ -class WireMockStubStrategy { - - private static final String STEP_START = "Started" - private static final String STEP_PREFIX = "Step" - private final WireMockRequestStubStrategy wireMockRequestStubStrategy - private final WireMockResponseStubStrategy wireMockResponseStubStrategy - private final Integer priority - private final ContractMetadata contract - private final String rootName - private final Contract groovyDsl - - WireMockStubStrategy(String rootName, ContractMetadata contract, Contract groovyDsl) { - this.rootName = rootName - this.contract = contract - SingleContractMetadata singleContractMetadata = contract.forContract(groovyDsl) - this.wireMockRequestStubStrategy = new WireMockRequestStubStrategy(groovyDsl, singleContractMetadata) - this.wireMockResponseStubStrategy = new WireMockResponseStubStrategy(groovyDsl, singleContractMetadata) - this.priority = groovyDsl.priority - this.groovyDsl = groovyDsl - } - - /** - * Converts {@link ContractMetadata} to {@link StubMapping} - */ - StubMapping toWireMockClientStub() { - StubMapping stubMapping = new StubMapping() - RequestPattern request = wireMockRequestStubStrategy.buildClientRequestContent() - ResponseDefinition response = wireMockResponseStubStrategy. - buildClientResponseContent() - if (priority) { - stubMapping.priority = priority - } - - stubMapping.request = request - stubMapping.response = response - - if (!request || !response) { - return null - } - - if (groovyDsl.ignored || contract.ignored) { - return null - } - - if (contract.order != null) { - stubMapping.scenarioName = "Scenario_" + rootName - stubMapping.requiredScenarioState = contract.order == 0 ? STEP_START : STEP_PREFIX + contract.order - if (contract.order < contract.groupSize - 1) { - stubMapping.newScenarioState = STEP_PREFIX + (contract.order + 1) - } - } - return stubMapping - } -} 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 deleted file mode 100644 index a30c45268d..0000000000 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.file - -import java.nio.file.Path - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString -import org.apache.commons.logging.Log -import org.apache.commons.logging.LogFactory - -import org.springframework.cloud.contract.spec.Contract -import org.springframework.cloud.contract.spec.internal.DslProperty -import org.springframework.cloud.contract.spec.internal.Header -import org.springframework.cloud.contract.spec.internal.Headers -import org.springframework.cloud.contract.verifier.util.ContentType -import org.springframework.cloud.contract.verifier.util.ContentUtils -import org.springframework.cloud.contract.verifier.util.NamesUtil -import org.springframework.util.Assert -/** - * Contains metadata for a particular file with a DSL - * - * @author Jakub Kubrynski, codearte.io - * - * @since 1.0.0 - */ -@CompileStatic -class ContractMetadata { - /** - * Path to the file - */ - final Path path - /** - * Should the contract be ignored - */ - final boolean ignored - /** - * How many files are there in the folder - */ - final int groupSize - /** - * If scenario related will contain an order of execution - */ - final Integer order - /** - * The list of contracts for the given file - */ - final Collection convertedContract = [] - /** - * Converted contracts with meta data information - */ - final Collection convertedContractWithMetadata = [] - - ContractMetadata(Path path, boolean ignored, int groupSize, Integer order, Contract convertedContract) { - this(path, ignored, groupSize, order, [convertedContract]) - } - - ContractMetadata(Path path, boolean ignored, int groupSize, Integer order, Collection convertedContract) { - this.groupSize = groupSize - this.path = path - this.ignored = ignored - this.order = order - this.convertedContract.addAll(convertedContract) - this.convertedContractWithMetadata.addAll( - this.convertedContract - .findAll { it != null } - .collect { new SingleContractMetadata(it, this) }) - } - - SingleContractMetadata forContract(Contract contract) { - return this.convertedContractWithMetadata - .find { it.contract == contract } - } - - boolean anyInProgress() { - return this.convertedContract.any { it.inProgress } - } -} - -@CompileStatic -@EqualsAndHashCode(excludes = ["contractMetadata"]) -@ToString(excludes = ["contractMetadata"]) -class SingleContractMetadata { - - private static final Log log = LogFactory.getLog(SingleContractMetadata) - - final ContractMetadata contractMetadata - private final File stubsFile - final Contract contract - private final Collection allContracts - final String definedInputStubContentType - final ContentType inputStubContentType - final ContentType evaluatedInputStubContentType - final String definedOutputStubContentType - final ContentType outputStubContentType - final ContentType evaluatedOutputStubContentType - final String definedInputTestContentType - final ContentType inputTestContentType - final ContentType evaluatedInputTestContentType - final String definedOutputTestContentType - final ContentType outputTestContentType - final ContentType evaluatedOutputTestContentType - String methodName - private final boolean http - - SingleContractMetadata(Contract currentContract, ContractMetadata contractMetadata) { - this.allContracts = contractMetadata.convertedContract - this.contract = currentContract - Assert.notNull(currentContract, "Contract must not be null") - Headers inputHeaders = inputHeaders(currentContract) - DslProperty inputBody = inputBody(currentContract) - Headers outputHeaders = outputHeaders(currentContract) - DslProperty outputBody = outputBody(currentContract) - Header inputContentType = contentTypeHeader(inputHeaders) - Header outputContentType = contentTypeHeader(outputHeaders) - this.definedInputTestContentType = inputContentType != null ? inputContentType.getServerValue() : "" - this.evaluatedInputTestContentType = tryToEvaluateTestContentType(inputHeaders, inputBody) - this.inputTestContentType = inputBody != null ? this.evaluatedInputTestContentType : ContentType.UNKNOWN - this.definedOutputTestContentType = outputContentType != null ? outputContentType.getServerValue() : "" - this.evaluatedOutputTestContentType = tryToEvaluateTestContentType(outputHeaders, outputBody) - this.outputTestContentType = outputBody != null ? this.evaluatedOutputTestContentType : ContentType.UNKNOWN - this.definedInputStubContentType = inputContentType != null ? inputContentType.getClientValue() : "" - this.evaluatedInputStubContentType = tryToEvaluateStubContentType(inputHeaders, inputBody) - this.inputStubContentType = inputBody != null ? this.evaluatedInputStubContentType : ContentType.UNKNOWN - this.definedOutputStubContentType = outputContentType != null ? outputContentType.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 Header contentTypeHeader(Headers headers) { - return headers == null ? null : headers.getEntries().stream() - .filter({ header -> "Content-Type".equalsIgnoreCase(header.getName()) }) - .findFirst().orElse(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 || - this.inputStubContentType == ContentType.JSON || - this.outputStubContentType == ContentType.JSON - } - - boolean evaluatesToJson() { - return isJson() || this.evaluatedInputTestContentType == ContentType.JSON || - this.evaluatedOutputTestContentType == ContentType.JSON || - this.evaluatedInputStubContentType == ContentType.JSON || - this.evaluatedOutputStubContentType == ContentType.JSON - } - - boolean isIgnored() { - return this.contract.ignored || this.contractMetadata.ignored - } - - boolean isXml() { - return this.inputTestContentType == ContentType.XML || - this.outputTestContentType == ContentType.XML || - this.inputStubContentType == ContentType.XML || - this.outputStubContentType == ContentType.XML - } - - boolean isHttp() { - return this.http - } - - boolean isInProgress() { - return this.contract.isInProgress() - } - - boolean isMessaging() { - return !isHttp() - } - - private DslProperty inputBody(Contract contract) { - return contract.request?.body ?: contract.input?.messageBody - } - - private Headers inputHeaders(Contract contract) { - return contract.request?.headers ?: contract.input?.messageHeaders - } - - private DslProperty outputBody(Contract contract) { - return contract.response?.body ?: contract.outputMessage?.body - } - - private Headers outputHeaders(Contract contract) { - return contract.response?.headers ?: contract.outputMessage?.headers - } - - String methodName() { - if (this.methodName == null) { - this.methodName = calculateMethodName() - } - return this.methodName - } - - private String calculateMethodName() { - if (contract.name) { - String name = NamesUtil. - camelCase(NamesUtil.convertIllegalPackageChars(contract.name)) - if (log.isDebugEnabled()) { - log.debug("Overriding the default test name with [" + name + "]") - } - return name - } - else if (allContracts.size() > 1) { - int index = allContracts.findIndexOf { it == contract } - String name = "${camelCasedMethodFromFileName(stubsFile)}_${index}" - if (log.isDebugEnabled()) { - log.debug("Scenario found. The method name will be [" + name + "]") - } - return name - } - String name = camelCasedMethodFromFileName(stubsFile) - if (log.isDebugEnabled()) { - log.debug("The method name will be [" + name + "]") - } - return name - } - - private static String camelCasedMethodFromFileName(File stubsFile) { - return NamesUtil.camelCase(NamesUtil.convertIllegalMethodNameChars(NamesUtil. - toLastDot(NamesUtil.afterLast(stubsFile.path, File.separator)))) - } -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.java new file mode 100755 index 0000000000..0f968d781f --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.java @@ -0,0 +1,133 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.verifier.dsl.wiremock; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.http.HttpHeader; +import com.github.tomakehurst.wiremock.http.HttpHeaders; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import groovy.lang.GString; + +import org.springframework.cloud.contract.spec.Contract; +import org.springframework.cloud.contract.spec.internal.FromFileProperty; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.spec.internal.Response; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.ContentType; +import org.springframework.cloud.contract.verifier.util.MapConverter; +import org.springframework.core.io.support.SpringFactoriesLoader; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +/** + * Converts a {@link Request} into {@link ResponseDefinition}. + * + * @author Marcin Grzejszczak + * @author Olga Maciaszek-Sharma + * @since 1.0.0 + */ +class WireMockResponseStubStrategy extends BaseWireMockStubStrategy { + + private final Response response; + + private final ContentType contentType; + + WireMockResponseStubStrategy(Contract groovyDsl, + SingleContractMetadata singleContractMetadata) { + super(groovyDsl); + this.response = groovyDsl.getResponse(); + this.contentType = contentType(singleContractMetadata); + } + + protected ContentType contentType(SingleContractMetadata singleContractMetadata) { + return singleContractMetadata.getEvaluatedOutputStubContentType(); + } + + ResponseDefinition buildClientResponseContent() { + if (response == null) { + return null; + } + ResponseDefinitionBuilder builder = new ResponseDefinitionBuilder().withStatus( + (Integer) MapConverter.getStubSideValues(response.getStatus())); + appendHeaders(builder); + appendBody(builder); + appendResponseDelayTime(builder); + builder.withTransformers(responseTransformerNames()); + return builder.build(); + } + + private String[] responseTransformerNames() { + List wireMockExtensions = SpringFactoriesLoader + .loadFactories(WireMockExtensions.class, null); + if (!wireMockExtensions.isEmpty()) { + return wireMockExtensions.stream().map(WireMockExtensions::extensions) + .flatMap(Collection::stream).map(Extension::getName) + .toArray(String[]::new); + } + return new String[] { new DefaultResponseTransformer().getName() }; + } + + private void appendHeaders(ResponseDefinitionBuilder builder) { + if (response.getHeaders() != null) { + HttpHeaders headers = response.getHeaders().getEntries().stream() + .map(it -> new HttpHeader(it.getName(), + MapConverter.getStubSideValues(it.getClientValue()) + .toString())) + .collect(collectingAndThen(toList(), HttpHeaders::new)); + builder.withHeaders(headers); + } + } + + private void appendBody(ResponseDefinitionBuilder builder) { + if (response.getBody() != null) { + Object body = MapConverter.getStubSideValues(response.getBody()); + if (body instanceof byte[]) { + builder.withBody((byte[]) body); + } + else if (body instanceof FromFileProperty + && ((FromFileProperty) body).isByte()) { + builder.withBody(((FromFileProperty) body).asBytes()); + } + else if (body instanceof Map) { + builder.withBody(parseBody((Map) body, contentType)); + } + else if (body instanceof List) { + builder.withBody(parseBody((List) body, contentType)); + } + else if (body instanceof GString) { + builder.withBody(parseBody((GString) body, contentType)); + } + else { + builder.withBody(parseBody(body, contentType)); + } + } + } + + private void appendResponseDelayTime(ResponseDefinitionBuilder builder) { + // TODO: Add a missing test for this + if (response.getDelay() != null) { + builder.withFixedDelay((Integer) response.getDelay().getClientValue()); + } + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.java new file mode 100644 index 0000000000..72904ef8c9 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockStubStrategy.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.verifier.dsl.wiremock; + +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; + +import org.springframework.cloud.contract.spec.Contract; +import org.springframework.cloud.contract.verifier.file.ContractMetadata; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +/** + * Converts a {@link ContractMetadata} into a WireMock {@link StubMapping}. + * + * @since 1.0.0 + */ +public class WireMockStubStrategy { + + private static final String STEP_START = "Started"; + + private static final String STEP_PREFIX = "Step"; + + private final WireMockRequestStubStrategy wireMockRequestStubStrategy; + + private final WireMockResponseStubStrategy wireMockResponseStubStrategy; + + private final Integer priority; + + private final ContractMetadata contract; + + private final String rootName; + + private final Contract groovyDsl; + + public WireMockStubStrategy(String rootName, ContractMetadata contract, + Contract groovyDsl) { + this.rootName = rootName; + this.contract = contract; + SingleContractMetadata singleContractMetadata = contract.forContract(groovyDsl); + this.wireMockRequestStubStrategy = new WireMockRequestStubStrategy(groovyDsl, + singleContractMetadata); + this.wireMockResponseStubStrategy = new WireMockResponseStubStrategy(groovyDsl, + singleContractMetadata); + this.priority = groovyDsl.getPriority(); + this.groovyDsl = groovyDsl; + } + + /** + * Converts {@link ContractMetadata} to {@link StubMapping}. + */ + public StubMapping toWireMockClientStub() { + StubMapping stubMapping = new StubMapping(); + RequestPattern request = wireMockRequestStubStrategy.buildClientRequestContent(); + ResponseDefinition response = wireMockResponseStubStrategy + .buildClientResponseContent(); + if (priority != null) { + stubMapping.setPriority(priority); + } + stubMapping.setRequest(request); + stubMapping.setResponse(response); + if (request == null || response == null) { + return null; + } + if (groovyDsl.getIgnored() || contract.getIgnored()) { + return null; + } + if (contract.getOrder() != null) { + stubMapping.setScenarioName("Scenario_" + rootName); + stubMapping.setRequiredScenarioState(contract.getOrder() == 0 ? STEP_START + : STEP_PREFIX + contract.getOrder()); + if (contract.getOrder() < contract.getGroupSize() - 1) { + stubMapping.setNewScenarioState(STEP_PREFIX + (contract.getOrder() + 1)); + } + } + return stubMapping; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.groovy b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.java similarity index 60% rename from spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.groovy rename to spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.java index 0e2e2cbee8..1d164c236c 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/dsl/wiremock/XPathBodyMatcherToWireMockValuePatternConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2013-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,28 @@ * limitations under the License. */ -package org.springframework.cloud.contract.verifier.dsl.wiremock +package org.springframework.cloud.contract.verifier.dsl.wiremock; -import com.github.tomakehurst.wiremock.client.WireMock -import com.github.tomakehurst.wiremock.matching.StringValuePattern -import groovy.transform.CompileStatic -import groovy.transform.PackageScope +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; -import org.springframework.cloud.contract.spec.internal.MatchingType - -import static org.springframework.cloud.contract.spec.internal.MatchingType.EQUALITY +import org.springframework.cloud.contract.spec.internal.MatchingType; /** * @author Olga Maciaszek-Sharma * @since 2.1.0 */ -@CompileStatic -@PackageScope -class XPathBodyMatcherToWireMockValuePatternConverter { +final class XPathBodyMatcherToWireMockValuePatternConverter { - static StringValuePattern mapToPattern(MatchingType type, String value) { - switch (type) { - case EQUALITY: return WireMock.equalTo(value) - default: return WireMock.matching(value) + private XPathBodyMatcherToWireMockValuePatternConverter() { + throw new IllegalStateException("Can't instantiate an utility class"); + } + + public static StringValuePattern mapToPattern(MatchingType type, String value) { + if (type == MatchingType.EQUALITY) { + return WireMock.equalTo(value); } + return WireMock.matching(value); } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/ContractMetadata.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/ContractMetadata.java new file mode 100644 index 0000000000..5565f7a007 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/ContractMetadata.java @@ -0,0 +1,122 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.verifier.file; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.springframework.cloud.contract.spec.Contract; + +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; + +/** + * Contains metadata for a particular file with a DSL. + * + * @author Jakub Kubrynski, codearte.io + * @since 1.0.0 + */ +public class ContractMetadata { + + /** + * Path to the file. + */ + private final Path path; + + /** + * Should the contract be ignored. + */ + private final boolean ignored; + + /** + * How many files are there in the folder. + */ + private final int groupSize; + + /** + * If scenario related will contain an order of execution. + */ + private final Integer order; + + /** + * The list of contracts for the given file. + */ + private final List convertedContract = new ArrayList<>(); + + /** + * Converted contracts with meta data information. + */ + private final Collection convertedContractWithMetadata = new ArrayList<>(); + + public ContractMetadata(Path path, boolean ignored, int groupSize, Integer order, + Contract convertedContract) { + this(path, ignored, groupSize, order, singletonList(convertedContract)); + } + + public ContractMetadata(Path path, boolean ignored, int groupSize, Integer order, + Collection convertedContract) { + this.groupSize = groupSize; + this.path = path; + this.ignored = ignored; + this.order = order; + this.convertedContract.addAll(convertedContract); + this.convertedContractWithMetadata.addAll(this.convertedContract.stream() + .filter(Objects::nonNull).map(it -> new SingleContractMetadata(it, this)) + .collect(toList())); + } + + public SingleContractMetadata forContract(Contract contract) { + return this.convertedContractWithMetadata.stream() + .filter(it -> it.getContract().equals(contract)).findFirst().orElse(null); + } + + public boolean anyInProgress() { + return this.convertedContract.stream().anyMatch(Contract::getInProgress); + } + + public Path getPath() { + return path; + } + + public boolean getIgnored() { + return ignored; + } + + public boolean isIgnored() { + return ignored; + } + + public int getGroupSize() { + return groupSize; + } + + public Integer getOrder() { + return order; + } + + public List getConvertedContract() { + return convertedContract; + } + + public Collection getConvertedContractWithMetadata() { + return convertedContractWithMetadata; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/SingleContractMetadata.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/SingleContractMetadata.java new file mode 100644 index 0000000000..94f787085b --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/file/SingleContractMetadata.java @@ -0,0 +1,315 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.verifier.file; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.contract.spec.Contract; +import org.springframework.cloud.contract.spec.internal.DslProperty; +import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.spec.internal.Headers; +import org.springframework.cloud.contract.spec.internal.Input; +import org.springframework.cloud.contract.spec.internal.OutputMessage; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.spec.internal.Response; +import org.springframework.cloud.contract.verifier.util.ContentType; +import org.springframework.util.Assert; + +import static org.springframework.cloud.contract.verifier.util.ContentType.DEFINED; +import static org.springframework.cloud.contract.verifier.util.ContentType.JSON; +import static org.springframework.cloud.contract.verifier.util.ContentType.UNKNOWN; +import static org.springframework.cloud.contract.verifier.util.ContentType.XML; +import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType; +import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateServerSideContentType; +import static org.springframework.cloud.contract.verifier.util.NamesUtil.afterLast; +import static org.springframework.cloud.contract.verifier.util.NamesUtil.camelCase; +import static org.springframework.cloud.contract.verifier.util.NamesUtil.convertIllegalMethodNameChars; +import static org.springframework.cloud.contract.verifier.util.NamesUtil.convertIllegalPackageChars; +import static org.springframework.cloud.contract.verifier.util.NamesUtil.isEmpty; +import static org.springframework.cloud.contract.verifier.util.NamesUtil.toLastDot; + +public class SingleContractMetadata { + + private static final Logger log = LoggerFactory + .getLogger(SingleContractMetadata.class); + + private final ContractMetadata contractMetadata; + + private final Path stubsPath; + + private final Contract contract; + + private final List allContracts; + + private final String definedInputStubContentType; + + private final ContentType inputStubContentType; + + private final ContentType evaluatedInputStubContentType; + + private final String definedOutputStubContentType; + + private final ContentType outputStubContentType; + + private final ContentType evaluatedOutputStubContentType; + + private final String definedInputTestContentType; + + private final ContentType inputTestContentType; + + private final ContentType evaluatedInputTestContentType; + + private final String definedOutputTestContentType; + + private final ContentType outputTestContentType; + + private final ContentType evaluatedOutputTestContentType; + + private String methodName; + + private final boolean http; + + public SingleContractMetadata(Contract currentContract, + ContractMetadata contractMetadata) { + Assert.notNull(currentContract, "Contract must not be null"); + this.allContracts = contractMetadata.getConvertedContract(); + this.contract = currentContract; + this.contractMetadata = contractMetadata; + Headers inputHeaders = inputHeaders(currentContract); + DslProperty inputBody = inputBody(currentContract); + Headers outputHeaders = outputHeaders(currentContract); + DslProperty outputBody = outputBody(currentContract); + Header inputContentType = contentTypeHeader(inputHeaders); + Header outputContentType = contentTypeHeader(outputHeaders); + this.definedInputTestContentType = Optional.ofNullable(inputContentType) + .map(DslProperty::getServerValue).map(Object::toString).orElse(""); + this.evaluatedInputTestContentType = tryToEvaluateTestContentType(inputHeaders, + inputBody); + this.inputTestContentType = inputBody != null ? this.evaluatedInputTestContentType + : UNKNOWN; + this.definedOutputTestContentType = Optional.ofNullable(outputContentType) + .map(DslProperty::getServerValue).map(Object::toString).orElse(""); + this.evaluatedOutputTestContentType = tryToEvaluateTestContentType(outputHeaders, + outputBody); + this.outputTestContentType = outputBody != null + ? this.evaluatedOutputTestContentType : UNKNOWN; + this.definedInputStubContentType = Optional.ofNullable(inputContentType) + .map(DslProperty::getClientValue).map(Object::toString).orElse(""); + this.evaluatedInputStubContentType = tryToEvaluateStubContentType(inputHeaders, + inputBody); + this.inputStubContentType = inputBody != null ? this.evaluatedInputStubContentType + : UNKNOWN; + this.definedOutputStubContentType = Optional.ofNullable(outputContentType) + .map(DslProperty::getClientValue).map(Object::toString).orElse(""); + this.evaluatedOutputStubContentType = tryToEvaluateStubContentType(outputHeaders, + outputBody); + this.outputStubContentType = outputBody != null + ? this.evaluatedOutputStubContentType : UNKNOWN; + this.http = currentContract.getRequest() != null; + this.stubsPath = contractMetadata.getPath(); + } + + private Header contentTypeHeader(Headers headers) { + return headers == null ? null + : headers.getEntries().stream() + .filter(it -> "Content-Type".equalsIgnoreCase(it.getName())) + .findFirst().orElse(null); + } + + private ContentType tryToEvaluateStubContentType(Headers mainHeaders, + DslProperty body) { + Object clientValue = Optional.ofNullable(body).map(DslProperty::getClientValue) + .orElse(null); + ContentType contentType = evaluateClientSideContentType(mainHeaders, clientValue); + if (contentType == DEFINED || contentType == UNKNOWN) { + // try to retrieve from the other side (e.g. stub side was a regex, but test + // side is concrete) + Object serverValue = Optional.ofNullable(body) + .map(DslProperty::getServerValue).orElse(null); + return evaluateServerSideContentType(mainHeaders, serverValue); + } + return contentType; + } + + private ContentType tryToEvaluateTestContentType(Headers mainHeaders, + DslProperty body) { + Object serverValue = Optional.ofNullable(body).map(DslProperty::getServerValue) + .orElse(null); + ContentType contentType = evaluateClientSideContentType(mainHeaders, serverValue); + if (contentType == DEFINED || contentType == UNKNOWN) { + // try to retrieve from the other side (e.g. stub side was a regex, but test + // side is concrete) + Object clientValue = Optional.ofNullable(body) + .map(DslProperty::getClientValue).orElse(null); + return evaluateServerSideContentType(mainHeaders, clientValue); + } + return contentType; + } + + public boolean isJson() { + return this.inputTestContentType.equals(JSON) + || this.outputTestContentType.equals(JSON) + || this.inputStubContentType.equals(JSON) + || this.outputStubContentType.equals(JSON); + } + + public boolean evaluatesToJson() { + return isJson() || this.evaluatedInputTestContentType.equals(JSON) + || this.evaluatedOutputTestContentType.equals(JSON) + || this.evaluatedInputStubContentType.equals(JSON) + || this.evaluatedOutputStubContentType.equals(JSON); + } + + public boolean isIgnored() { + return this.contract.getIgnored() || this.contractMetadata.getIgnored(); + } + + public boolean isXml() { + return this.inputTestContentType.equals(XML) + || this.outputTestContentType.equals(XML) + || this.inputStubContentType.equals(XML) + || this.outputStubContentType.equals(XML); + } + + public boolean isHttp() { + return this.http; + } + + public boolean isInProgress() { + return this.contract.isInProgress(); + } + + public boolean isMessaging() { + return !isHttp(); + } + + private DslProperty inputBody(Contract contract) { + return Optional.ofNullable(contract.getRequest()).map(Request::getBody) + .map(DslProperty.class::cast) + .orElseGet(() -> Optional.ofNullable(contract.getInput()) + .map(Input::getMessageBody).orElse(null)); + } + + private Headers inputHeaders(Contract contract) { + return Optional.ofNullable(contract.getRequest()).map(Request::getHeaders) + .orElseGet(() -> Optional.ofNullable(contract.getInput()) + .map(Input::getMessageHeaders).orElse(null)); + } + + private DslProperty outputBody(Contract contract) { + return Optional.ofNullable(contract.getResponse()).map(Response::getBody) + .map(DslProperty.class::cast) + .orElseGet(() -> Optional.ofNullable(contract.getOutputMessage()) + .map(OutputMessage::getBody).orElse(null)); + } + + private Headers outputHeaders(Contract contract) { + return Optional.ofNullable(contract.getResponse()).map(Response::getHeaders) + .orElseGet(() -> Optional.ofNullable(contract.getOutputMessage()) + .map(OutputMessage::getHeaders).orElse(null)); + } + + public String methodName() { + if (this.methodName == null) { + this.methodName = calculateMethodName(); + } + return this.methodName; + } + + private String calculateMethodName() { + if (!isEmpty(contract.getName())) { + String name = camelCase(convertIllegalPackageChars(contract.getName())); + log.debug("Overriding the default test name with [{}]", name); + return name; + } + if (allContracts.size() > 1) { + int index = allContracts.indexOf(getContract()); + String name = String.format("%s_%d", camelCasedMethodFromFileName(stubsPath), + index); + log.debug("Scenario found. The method name will be [{}]", name); + return name; + } + String name = camelCasedMethodFromFileName(stubsPath); + log.debug("The method name will be [{}]", name); + return name; + } + + private static String camelCasedMethodFromFileName(Path stubsPath) { + return camelCase(convertIllegalMethodNameChars( + toLastDot(afterLast(stubsPath.toString(), File.separator)))); + } + + public ContractMetadata getContractMetadata() { + return contractMetadata; + } + + public Contract getContract() { + return contract; + } + + public Collection getAllContracts() { + return allContracts; + } + + public String getDefinedInputStubContentType() { + return definedInputStubContentType; + } + + public ContentType getInputStubContentType() { + return inputStubContentType; + } + + public ContentType getEvaluatedInputStubContentType() { + return evaluatedInputStubContentType; + } + + public String getDefinedOutputStubContentType() { + return definedOutputStubContentType; + } + + public ContentType getEvaluatedOutputStubContentType() { + return evaluatedOutputStubContentType; + } + + public String getDefinedInputTestContentType() { + return definedInputTestContentType; + } + + public ContentType getInputTestContentType() { + return inputTestContentType; + } + + public String getDefinedOutputTestContentType() { + return definedOutputTestContentType; + } + + public ContentType getOutputTestContentType() { + return outputTestContentType; + } + + public ContentType getEvaluatedOutputTestContentType() { + return evaluatedOutputTestContentType; + } + +}