From 2121b0b1b41c9012b57e5753c89ac14bc536ee6f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 14 Sep 2020 17:37:59 +0200 Subject: [PATCH] Added a CUSTOM test mode for test generation --- .../pom.xml | 4 + spring-cloud-contract-verifier/pom.xml | 5 + .../verifier/builder/CustomModeAcceptor.java | 39 +++ .../verifier/builder/CustomModeBodyGiven.java | 94 +++++++ .../builder/CustomModeBodyParser.java | 34 +++ .../builder/CustomModeCookiesGiven.java | 74 +++++ .../builder/CustomModeCookiesThen.java | 61 +++++ .../verifier/builder/CustomModeFields.java | 46 ++++ .../verifier/builder/CustomModeGiven.java | 74 +++++ .../builder/CustomModeHeadersGiven.java | 78 ++++++ .../builder/CustomModeHeadersThen.java | 89 ++++++ .../verifier/builder/CustomModeImports.java | 50 ++++ .../builder/CustomModeMethodWithUrlGiven.java | 69 +++++ .../builder/CustomModeRequestBuildGiven.java | 42 +++ .../builder/CustomModeRequestGiven.java | 43 +++ .../builder/CustomModeResponseWhen.java | 45 +++ .../builder/CustomModeStaticImports.java | 48 ++++ .../builder/CustomModeStatusCodeThen.java | 49 ++++ .../verifier/builder/CustomModeThen.java | 58 ++++ .../verifier/builder/CustomModeWhen.java | 62 +++++ .../builder/CustomMultipartGiven.java | 42 +++ .../builder/CustomQueryParamsGiven.java | 47 ++++ .../verifier/builder/FieldBuilder.java | 5 + .../verifier/builder/ImportsBuilder.java | 12 +- ...partGiven.java => JavaMultipartGiven.java} | 35 ++- .../verifier/builder/JavaTestGenerator.java | 5 +- .../verifier/builder/RestAssuredAcceptor.java | 32 +++ .../builder/RestAssuredCookiesThen.java | 3 +- .../verifier/builder/RestAssuredGiven.java | 29 +- .../builder/RestAssuredHeadersThen.java | 2 +- .../verifier/builder/RestAssuredThen.java | 15 +- .../verifier/builder/RestAssuredWhen.java | 29 +- .../verifier/builder/SingleMethodBuilder.java | 14 + .../builder/SpockMockMvcMultipartGiven.java | 38 ++- .../{CustomImports.java => UserImports.java} | 5 +- ...ticImports.java => UserStaticImports.java} | 8 +- .../builder/imports/BaseImportProvider.java | 96 ------- .../builder/imports/HttpImportProvider.java | 144 ---------- .../builder/imports/ImportDefinitions.java | 50 ---- .../imports/MessagingImportProvider.java | 50 ---- .../contract/verifier/config/TestMode.java | 6 + .../cloud/contract/verifier/http/Body.java | 68 +++++ .../http/ContractVerifierHttpMetadata.java | 194 +++++++++++++ .../contract/verifier/http/HttpVerifier.java | 36 +++ .../verifier/http/OkHttpHttpVerifier.java | 149 ++++++++++ .../cloud/contract/verifier/http/Request.java | 256 ++++++++++++++++++ .../contract/verifier/http/Response.java | 171 ++++++++++++ .../YamlMockMvcMethodBodyBuilderSpec.groovy | 45 +++ ...pringCloudContractRequestMatcherTests.java | 41 +-- 49 files changed, 2263 insertions(+), 428 deletions(-) create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeAcceptor.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyParser.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesThen.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeFields.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersThen.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeImports.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeMethodWithUrlGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestBuildGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeResponseWhen.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStaticImports.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStatusCodeThen.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeThen.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeWhen.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomMultipartGiven.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomQueryParamsGiven.java rename spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/{MockMvcMultipartGiven.java => JavaMultipartGiven.java} (74%) create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredAcceptor.java rename spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/{CustomImports.java => UserImports.java} (90%) rename spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/{CustomStaticImports.java => UserStaticImports.java} (84%) delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/BaseImportProvider.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/HttpImportProvider.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/ImportDefinitions.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/MessagingImportProvider.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Body.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/HttpVerifier.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java create mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Response.java diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml index b9b880936d..fcb3dc3d79 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml @@ -265,6 +265,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework + spring-jcl + org.apache.maven.resolver diff --git a/spring-cloud-contract-verifier/pom.xml b/spring-cloud-contract-verifier/pom.xml index 1d30c776b8..cfa99d5d05 100644 --- a/spring-cloud-contract-verifier/pom.xml +++ b/spring-cloud-contract-verifier/pom.xml @@ -33,6 +33,11 @@ com.fasterxml.jackson.core jackson-databind + + com.squareup.okhttp3 + okhttp + true + org.springframework spring-messaging diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeAcceptor.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeAcceptor.java new file mode 100644 index 0000000000..66f3b7e663 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeAcceptor.java @@ -0,0 +1,39 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.verifier.config.TestMode; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +interface CustomModeAcceptor { + + default boolean acceptType(GeneratedClassMetaData generatedClassMetaData, + SingleContractMetadata singleContractMetadata) { + return generatedClassMetaData.configProperties.getTestMode() == TestMode.CUSTOM + && acceptType(singleContractMetadata); + } + + default boolean acceptType(GeneratedClassMetaData generatedClassMetaData) { + return generatedClassMetaData.configProperties.getTestMode() == TestMode.CUSTOM + && generatedClassMetaData.isAnyHttp(); + } + + default boolean acceptType(SingleContractMetadata singleContractMetadata) { + return singleContractMetadata.isHttp(); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyGiven.java new file mode 100644 index 0000000000..af1b53a628 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyGiven.java @@ -0,0 +1,94 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.ExecutionProperty; +import org.springframework.cloud.contract.spec.internal.FromFileProperty; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.ContentType; + +class CustomModeBodyGiven implements Given { + + private final BlockBuilder blockBuilder; + + private final BodyReader bodyReader; + + private final BodyParser bodyParser; + + CustomModeBodyGiven(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { + this.blockBuilder = blockBuilder; + this.bodyReader = new BodyReader(generatedClassMetaData); + this.bodyParser = bodyParser; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + processInput(this.blockBuilder, metadata); + return this; + } + + private void processInput(BlockBuilder bb, SingleContractMetadata metadata) { + Object body; + Request request = metadata.getContract().getRequest(); + Object serverValue = request.getBody().getServerValue(); + if (serverValue instanceof ExecutionProperty + || serverValue instanceof FromFileProperty) { + body = request.getBody().getServerValue(); + } + else { + body = this.bodyParser.requestBodyAsString(metadata); + } + bb.addIndented(getBodyString(metadata, body)); + } + + private String getBodyString(SingleContractMetadata metadata, Object body) { + String value; + if (body instanceof ExecutionProperty) { + value = body.toString(); + } + else if (body instanceof FromFileProperty) { + FromFileProperty fileProperty = (FromFileProperty) body; + value = fileProperty.isByte() + ? this.bodyReader.readBytesFromFileString(metadata, fileProperty, + CommunicationType.REQUEST) + : this.bodyReader.readStringFromFileString(metadata, fileProperty, + CommunicationType.REQUEST); + } + else { + String escaped = escapeRequestSpecialChars(metadata, body.toString()); + value = this.bodyParser.quotedEscapedLongText(escaped); + } + return ".body(" + value + ")"; + } + + private String escapeRequestSpecialChars(SingleContractMetadata metadata, + String string) { + if (metadata.getInputTestContentType() == ContentType.JSON) { + return string.replaceAll("\\\\n", "\\\\\\\\n"); + } + return string; + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + return request != null && request.getBody() != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyParser.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyParser.java new file mode 100644 index 0000000000..621a6d03b2 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeBodyParser.java @@ -0,0 +1,34 @@ +/* + * 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.builder; + +interface CustomModeBodyParser extends BodyParser { + + BodyParser INSTANCE = new CustomModeBodyParser() { + }; + + @Override + default String responseAsString() { + return "response.getBody().asString()"; + } + + @Override + default String byteArrayString() { + return "response.getBody().asByteArray()"; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesGiven.java new file mode 100644 index 0000000000..12ef6734ec --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesGiven.java @@ -0,0 +1,74 @@ +/* + * 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.builder; + +import java.util.Iterator; + +import org.springframework.cloud.contract.spec.internal.Cookie; +import org.springframework.cloud.contract.spec.internal.MatchingStrategy; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeCookiesGiven implements Given { + + private final BlockBuilder blockBuilder; + + CustomModeCookiesGiven(BlockBuilder blockBuilder) { + this.blockBuilder = blockBuilder; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + processInput(metadata.getContract().getRequest()); + return this; + } + + private void processInput(Request request) { + Iterator iterator = request.getCookies().getEntries().iterator(); + while (iterator.hasNext()) { + Cookie cookie = iterator.next(); + if (ofAbsentType(cookie)) { + return; + } + if (iterator.hasNext()) { + this.blockBuilder.addLine(string(cookie)); + } + else { + this.blockBuilder.addIndented(string(cookie)); + } + } + } + + private String string(Cookie cookie) { + return ".cookie(" + ContentHelper.getTestSideForNonBodyValue(cookie.getKey()) + + ", " + ContentHelper.getTestSideForNonBodyValue(cookie.getServerValue()) + + ")"; + } + + private boolean ofAbsentType(Cookie cookie) { + return cookie.getServerValue() instanceof MatchingStrategy + && MatchingStrategy.Type.ABSENT + .equals(((MatchingStrategy) cookie.getServerValue()).getType()); + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + return request != null && request.getCookies() != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesThen.java new file mode 100644 index 0000000000..e6c4a47ba0 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeCookiesThen.java @@ -0,0 +1,61 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.Response; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeCookiesThen implements Then, CustomModeAcceptor, CookieElementProcessor { + + private final BlockBuilder blockBuilder; + + private final ComparisonBuilder comparisonBuilder; + + CustomModeCookiesThen(BlockBuilder blockBuilder, + ComparisonBuilder comparisonBuilder) { + this.blockBuilder = blockBuilder; + this.comparisonBuilder = comparisonBuilder; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + processCookies(metadata); + return this; + } + + @Override + public ComparisonBuilder comparisonBuilder() { + return this.comparisonBuilder; + } + + @Override + public BlockBuilder blockBuilder() { + return this.blockBuilder; + } + + @Override + public String cookieKey(String key) { + return "response.cookie(\"" + key + "\")"; + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Response response = metadata.getContract().getResponse(); + return response.getCookies() != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeFields.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeFields.java new file mode 100644 index 0000000000..cd16ebb3e3 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeFields.java @@ -0,0 +1,46 @@ +/* + * 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.builder; + +import java.util.Arrays; + +class CustomModeFields implements Field, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + private static final String[] FIELDS = { "@Inject HttpVerifier httpVerifier" }; + + CustomModeFields(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = generatedClassMetaData; + } + + @Override + public Field call() { + Arrays.stream(FIELDS).forEach(this.blockBuilder::addLineWithEnding); + return this; + } + + @Override + public boolean accept() { + return acceptType(this.generatedClassMetaData); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java new file mode 100644 index 0000000000..ee6bf48658 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java @@ -0,0 +1,74 @@ +/* + * 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.builder; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeGiven implements Given, BodyMethodVisitor, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + private final List requestGivens = new LinkedList<>(); + + private final List bodyGivens = new LinkedList<>(); + + CustomModeGiven(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = generatedClassMetaData; + this.requestGivens.addAll(Collections.singletonList( + new CustomModeRequestGiven(blockBuilder, generatedClassMetaData))); + this.bodyGivens.addAll( + Arrays.asList(new CustomModeMethodWithUrlGiven(blockBuilder, bodyParser), + new CustomModeHeadersGiven(blockBuilder), + new CustomModeCookiesGiven(blockBuilder), + new CustomModeBodyGiven(blockBuilder, generatedClassMetaData, + bodyParser), + new CustomMultipartGiven(generatedClassMetaData), + new CustomModeRequestBuildGiven(blockBuilder))); + } + + @Override + public MethodVisitor apply(SingleContractMetadata singleContractMetadata) { + startBodyBlock(this.blockBuilder, "given:"); + addRequestGivenLine(singleContractMetadata); + indentedBodyBlock(this.blockBuilder, this.bodyGivens, singleContractMetadata); + this.blockBuilder.addEmptyLine(); + return this; + } + + private void addRequestGivenLine(SingleContractMetadata singleContractMetadata) { + this.requestGivens.stream().filter(given -> given.accept(singleContractMetadata)) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "No matching request building Given implementation for a custom test mode")) + .apply(singleContractMetadata); + } + + @Override + public boolean accept(SingleContractMetadata singleContractMetadata) { + return acceptType(generatedClassMetaData, singleContractMetadata); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersGiven.java new file mode 100644 index 0000000000..2e3f40841f --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersGiven.java @@ -0,0 +1,78 @@ +/* + * 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.builder; + +import java.util.Iterator; + +import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.spec.internal.Headers; +import org.springframework.cloud.contract.spec.internal.MatchingStrategy; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; + +class CustomModeHeadersGiven implements Given { + + private final BlockBuilder blockBuilder; + + CustomModeHeadersGiven(BlockBuilder blockBuilder) { + this.blockBuilder = blockBuilder; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + processInput(this.blockBuilder, metadata.getContract().getRequest().getHeaders()); + return this; + } + + private void processInput(BlockBuilder bb, Headers headers) { + Iterator
iterator = headers.getEntries().iterator(); + while (iterator.hasNext()) { + Header header = iterator.next(); + if (ofAbsentType(header)) { + return; + } + if (iterator.hasNext()) { + bb.addLine(string(header)); + } + else { + bb.addIndented(string(header)); + } + } + } + + private String string(Header header) { + return ".header(" + ContentHelper.getTestSideForNonBodyValue(header.getName()) + + ", " + + ContentHelper.getTestSideForNonBodyValue( + MapConverter.getTestSideValuesForNonBody(header.getServerValue())) + + ")"; + } + + private boolean ofAbsentType(Header header) { + return header.getServerValue() instanceof MatchingStrategy + && MatchingStrategy.Type.ABSENT + .equals(((MatchingStrategy) header.getServerValue()).getType()); + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + return request != null && request.getHeaders() != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersThen.java new file mode 100644 index 0000000000..8785876388 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeHeadersThen.java @@ -0,0 +1,89 @@ +/* + * 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.builder; + +import java.util.Iterator; + +import org.springframework.cloud.contract.spec.internal.ExecutionProperty; +import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.spec.internal.Headers; +import org.springframework.cloud.contract.spec.internal.NotToEscapePattern; +import org.springframework.cloud.contract.spec.internal.Response; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; + +class CustomModeHeadersThen implements Then, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final ComparisonBuilder comparisonBuilder; + + CustomModeHeadersThen(BlockBuilder blockBuilder, + ComparisonBuilder comparisonBuilder) { + this.blockBuilder = blockBuilder; + this.comparisonBuilder = comparisonBuilder; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + Response response = metadata.getContract().getResponse(); + Headers headers = response.getHeaders(); + Iterator
iterator = headers.getEntries().iterator(); + while (iterator.hasNext()) { + Header header = iterator.next(); + String text = processHeaderElement(header.getName(), + header.getServerValue() instanceof NotToEscapePattern + ? header.getServerValue() + : MapConverter.getTestSideValues(header.getServerValue())); + if (iterator.hasNext()) { + this.blockBuilder.addLineWithEnding(text); + } + else { + this.blockBuilder.addIndented(text); + } + } + this.blockBuilder.addEndingIfNotPresent(); + return this; + } + + private String processHeaderElement(String property, Object value) { + if (value instanceof NotToEscapePattern) { + return this.comparisonBuilder + .assertThat("response.header(\"" + property + "\")") + + matchesManuallyEscapedPattern((NotToEscapePattern) value); + } + else if (value instanceof ExecutionProperty) { + return ((ExecutionProperty) value) + .insertValue("response.header(\"" + property + "\")"); + + } + return this.comparisonBuilder.assertThat("response.header(\"" + property + "\")", + value); + } + + private String matchesManuallyEscapedPattern(NotToEscapePattern value) { + return this.comparisonBuilder + .matchesEscaped(value.getServerValue().pattern().replace("\\", "\\\\")); + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Response response = metadata.getContract().getResponse(); + return response.getHeaders() != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeImports.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeImports.java new file mode 100644 index 0000000000..f61f9d4bb4 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeImports.java @@ -0,0 +1,50 @@ +/* + * 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.builder; + +import java.util.Arrays; + +class CustomModeImports implements Imports, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + private static final String[] IMPORTS = { "javax.inject.Inject", + "org.springframework.cloud.contract.verifier.http.HttpVerifier", + "org.springframework.cloud.contract.verifier.http.Request", + "org.springframework.cloud.contract.verifier.http.Response;" }; + + CustomModeImports(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = generatedClassMetaData; + } + + @Override + public Imports call() { + Arrays.stream(IMPORTS) + .forEach(s -> this.blockBuilder.addLineWithEnding("import " + s)); + return this; + } + + @Override + public boolean accept() { + return acceptType(this.generatedClassMetaData); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeMethodWithUrlGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeMethodWithUrlGiven.java new file mode 100644 index 0000000000..0a02fbafef --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeMethodWithUrlGiven.java @@ -0,0 +1,69 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.ExecutionProperty; +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.spec.internal.Url; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; +import org.springframework.cloud.contract.verifier.util.MapConverter; + +class CustomModeMethodWithUrlGiven implements Given { + + private final BlockBuilder blockBuilder; + + private final BodyParser bodyParser; + + CustomModeMethodWithUrlGiven(BlockBuilder blockBuilder, BodyParser bodyParser) { + this.blockBuilder = blockBuilder; + this.bodyParser = bodyParser; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + addUrl(getUrl(metadata.getContract().getRequest()), + metadata.getContract().getRequest()); + return this; + } + + private Url getUrl(Request request) { + if (request.getUrl() != null) { + return request.getUrl(); + } + if (request.getUrlPath() != null) { + return request.getUrlPath(); + } + throw new IllegalStateException("URL is not set!"); + } + + private void addUrl(Url buildUrl, Request request) { + Object testSideUrl = MapConverter.getTestSideValues(buildUrl); + String method = request.getMethod().getServerValue().toString().toLowerCase(); + String url = testSideUrl.toString(); + if (!(testSideUrl instanceof ExecutionProperty)) { + url = this.bodyParser.quotedShortText(testSideUrl.toString()); + } + this.blockBuilder.addIndented("." + method + "(" + url + ")"); + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + return request != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestBuildGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestBuildGiven.java new file mode 100644 index 0000000000..6118d01e45 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestBuildGiven.java @@ -0,0 +1,42 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeRequestBuildGiven implements Given { + + private final BlockBuilder blockBuilder; + + CustomModeRequestBuildGiven(BlockBuilder blockBuilder) { + this.blockBuilder = blockBuilder; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + this.blockBuilder.addLineWithEnding(".build()"); + return this; + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + return request != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestGiven.java new file mode 100644 index 0000000000..d3e899d82c --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeRequestGiven.java @@ -0,0 +1,43 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeRequestGiven implements Given, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + CustomModeRequestGiven(BlockBuilder blockBuilder, GeneratedClassMetaData metaData) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = metaData; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + this.blockBuilder.addIndented("Request request = given()"); + return this; + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + return acceptType(this.generatedClassMetaData); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeResponseWhen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeResponseWhen.java new file mode 100644 index 0000000000..533ba64b22 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeResponseWhen.java @@ -0,0 +1,45 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeResponseWhen implements When, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + CustomModeResponseWhen(BlockBuilder blockBuilder, GeneratedClassMetaData metaData) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = metaData; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + this.blockBuilder + .addLineWithEnding("Response response = httpVerifier.exchange(request)") + .endBlock(); + return this; + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + return acceptType(this.generatedClassMetaData, metadata); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStaticImports.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStaticImports.java new file mode 100644 index 0000000000..d1621006b6 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStaticImports.java @@ -0,0 +1,48 @@ +/* + * 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.builder; + +import java.util.Arrays; + +class CustomModeStaticImports implements Imports, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + private static final String[] IMPORTS = { + "org.springframework.cloud.contract.verifier.http.Request.given" }; + + CustomModeStaticImports(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = generatedClassMetaData; + } + + @Override + public Imports call() { + Arrays.stream(IMPORTS) + .forEach(s -> this.blockBuilder.addLineWithEnding("import static " + s)); + return this; + } + + @Override + public boolean accept() { + return acceptType(this.generatedClassMetaData); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStatusCodeThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStatusCodeThen.java new file mode 100644 index 0000000000..01100378ee --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeStatusCodeThen.java @@ -0,0 +1,49 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.Response; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeStatusCodeThen implements Then { + + private final BlockBuilder blockBuilder; + + private final ComparisonBuilder comparisonBuilder; + + CustomModeStatusCodeThen(BlockBuilder blockBuilder, + ComparisonBuilder comparisonBuilder) { + this.blockBuilder = blockBuilder; + this.comparisonBuilder = comparisonBuilder; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + Response response = metadata.getContract().getResponse(); + this.blockBuilder + .addIndented(this.comparisonBuilder.assertThat("response.statusCode()", + response.getStatus().getServerValue())) + .addEndingIfNotPresent(); + return this; + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + return true; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeThen.java new file mode 100644 index 0000000000..5270e54355 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeThen.java @@ -0,0 +1,58 @@ +/* + * 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.builder; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeThen implements Then, BodyMethodVisitor, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + private final List thens = new LinkedList<>(); + + CustomModeThen(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser, + ComparisonBuilder comparisonBuilder) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = generatedClassMetaData; + this.thens.addAll(Arrays.asList( + new CustomModeStatusCodeThen(this.blockBuilder, comparisonBuilder), + new CustomModeHeadersThen(this.blockBuilder, comparisonBuilder), + new CustomModeCookiesThen(this.blockBuilder, comparisonBuilder), + new GenericHttpBodyThen(this.blockBuilder, generatedClassMetaData, + bodyParser, comparisonBuilder))); + } + + @Override + public MethodVisitor apply(SingleContractMetadata singleContractMetadata) { + startBodyBlock(this.blockBuilder, "then:"); + bodyBlock(this.blockBuilder, this.thens, singleContractMetadata); + return this; + } + + @Override + public boolean accept(SingleContractMetadata singleContractMetadata) { + return acceptType(this.generatedClassMetaData, singleContractMetadata); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeWhen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeWhen.java new file mode 100644 index 0000000000..4ffa7670ca --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeWhen.java @@ -0,0 +1,62 @@ +/* + * 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.builder; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomModeWhen implements When, BodyMethodVisitor, CustomModeAcceptor { + + private final BlockBuilder blockBuilder; + + private final GeneratedClassMetaData generatedClassMetaData; + + private final List responseWhens = new LinkedList<>(); + + CustomModeWhen(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData) { + this.blockBuilder = blockBuilder; + this.generatedClassMetaData = generatedClassMetaData; + this.responseWhens.addAll(Arrays.asList( + new CustomModeResponseWhen(blockBuilder, this.generatedClassMetaData))); + } + + @Override + public MethodVisitor apply(SingleContractMetadata singleContractMetadata) { + startBodyBlock(this.blockBuilder, "when:"); + addResponseWhenLine(singleContractMetadata); + this.blockBuilder.addEmptyLine(); + return this; + } + + private void addResponseWhenLine(SingleContractMetadata singleContractMetadata) { + this.responseWhens.stream().filter(when -> when.accept(singleContractMetadata)) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "No matching request building When implementation for Rest Assured")) + .apply(singleContractMetadata); + } + + @Override + public boolean accept(SingleContractMetadata singleContractMetadata) { + return acceptType(this.generatedClassMetaData, singleContractMetadata); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomMultipartGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomMultipartGiven.java new file mode 100644 index 0000000000..79289da7ca --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomMultipartGiven.java @@ -0,0 +1,42 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomMultipartGiven implements Given, CustomModeAcceptor { + + private final GeneratedClassMetaData generatedClassMetaData; + + CustomMultipartGiven(GeneratedClassMetaData generatedClassMetaData) { + this.generatedClassMetaData = generatedClassMetaData; + } + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + throw new UnsupportedOperationException("Multipart is not yet supported"); + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + return request != null && request.getMultipart() != null + && acceptType(this.generatedClassMetaData, metadata); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomQueryParamsGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomQueryParamsGiven.java new file mode 100644 index 0000000000..baa39ff651 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomQueryParamsGiven.java @@ -0,0 +1,47 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.spec.internal.Request; +import org.springframework.cloud.contract.spec.internal.Url; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +class CustomQueryParamsGiven implements Given, CustomModeAcceptor, QueryParamsResolver { + + @Override + public MethodVisitor apply(SingleContractMetadata metadata) { + throw new UnsupportedOperationException("Query params are not supported"); + } + + private Url getUrl(Request request) { + if (request.getUrl() != null) { + return request.getUrl(); + } + if (request.getUrlPath() != null) { + return request.getUrlPath(); + } + throw new IllegalStateException("URL is not set!"); + } + + @Override + public boolean accept(SingleContractMetadata metadata) { + Request request = metadata.getContract().getRequest(); + Url url = getUrl(request); + return url.getQueryParameters() != null; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/FieldBuilder.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/FieldBuilder.java index 19d3a34b6d..d597e8f136 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/FieldBuilder.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/FieldBuilder.java @@ -35,6 +35,11 @@ class FieldBuilder { return this; } + FieldBuilder customMode() { + this.parentBuilder.field(new CustomModeFields(this.builder, this.metaData)); + return this; + } + ClassBodyBuilder build() { return this.parentBuilder; } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/ImportsBuilder.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/ImportsBuilder.java index 2e5c529306..9a9b405008 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/ImportsBuilder.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/ImportsBuilder.java @@ -36,9 +36,15 @@ class ImportsBuilder { return this; } - ImportsBuilder custom() { - this.parentBuilder.imports(new CustomImports(builder, metaData)); - this.parentBuilder.staticImports(new CustomStaticImports(builder, metaData)); + ImportsBuilder userImports() { + this.parentBuilder.imports(new UserImports(builder, metaData)); + this.parentBuilder.staticImports(new UserStaticImports(builder, metaData)); + return this; + } + + ImportsBuilder customMode() { + this.parentBuilder.imports(new CustomModeImports(builder, metaData)); + this.parentBuilder.staticImports(new CustomModeStaticImports(builder, metaData)); return this; } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaMultipartGiven.java similarity index 74% rename from spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java rename to spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaMultipartGiven.java index 0bd899c547..95ebeebd67 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/MockMvcMultipartGiven.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaMultipartGiven.java @@ -26,7 +26,7 @@ import org.springframework.cloud.contract.verifier.util.MapConverter; import static org.springframework.cloud.contract.verifier.util.ContentUtils.getJavaMultipartFileParameterContent; -class MockMvcMultipartGiven implements Given { +class JavaMultipartGiven implements Given, RestAssuredAcceptor { private final BlockBuilder blockBuilder; @@ -36,8 +36,8 @@ class MockMvcMultipartGiven implements Given { private final BodyParser bodyParser; - MockMvcMultipartGiven(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData, - BodyParser bodyParser) { + JavaMultipartGiven(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { this.blockBuilder = blockBuilder; this.bodyReader = new BodyReader(generatedClassMetaData); this.bodyParser = bodyParser; @@ -46,40 +46,47 @@ class MockMvcMultipartGiven implements Given { @Override public MethodVisitor apply(SingleContractMetadata metadata) { - getMultipartParameters(metadata).entrySet() - .forEach(entry -> this.blockBuilder.addLine(getMultipartParameterLine(metadata, entry))); + getMultipartParameters(metadata).entrySet().forEach(entry -> this.blockBuilder + .addLine(getMultipartParameterLine(metadata, entry))); return this; } - private String getMultipartParameterLine(SingleContractMetadata metadata, Map.Entry parameter) { + private String getMultipartParameterLine(SingleContractMetadata metadata, + Map.Entry parameter) { if (parameter.getValue() instanceof NamedProperty) { - return ".multiPart(" + getMultipartFileParameterContent(metadata, parameter.getKey(), - (NamedProperty) parameter.getValue()) + ")"; + return ".multiPart(" + getMultipartFileParameterContent(metadata, + parameter.getKey(), (NamedProperty) parameter.getValue()) + ")"; } return getParameterString(parameter); } @SuppressWarnings("unchecked") private Map getMultipartParameters(SingleContractMetadata metadata) { - return (Map) metadata.getContract().getRequest().getMultipart().getServerValue(); + return (Map) metadata.getContract().getRequest().getMultipart() + .getServerValue(); } - private String getMultipartFileParameterContent(SingleContractMetadata metadata, String propertyName, - NamedProperty propertyValue) { + private String getMultipartFileParameterContent(SingleContractMetadata metadata, + String propertyName, NamedProperty propertyValue) { return getJavaMultipartFileParameterContent(propertyName, propertyValue, - fileProp -> this.bodyReader.readBytesFromFileString(metadata, fileProp, CommunicationType.REQUEST)); + fileProp -> this.bodyReader.readBytesFromFileString(metadata, fileProp, + CommunicationType.REQUEST)); } private String getParameterString(Map.Entry parameter) { return ".param(" + this.bodyParser.quotedShortText(parameter.getKey()) + ", " - + this.bodyParser.quotedShortText(MapConverter.getTestSideValuesForNonBody(parameter.getValue())) + ")"; + + this.bodyParser.quotedShortText( + MapConverter.getTestSideValuesForNonBody(parameter.getValue())) + + ")"; } @Override public boolean accept(SingleContractMetadata metadata) { Request request = metadata.getContract().getRequest(); return request != null && request.getMultipart() != null - && this.generatedClassMetaData.configProperties.getTestFramework() != TestFramework.SPOCK; + && acceptType(this.generatedClassMetaData, metadata) + && this.generatedClassMetaData.configProperties + .getTestFramework() != TestFramework.SPOCK; } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java index e1f2241de0..175ef46959 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java @@ -66,7 +66,8 @@ public class JavaTestGenerator implements SingleTestGenerator { .build() .imports() .defaultImports() - .custom() + .userImports() + .customMode() .json() .jUnit4() .jUnit5() @@ -93,6 +94,7 @@ public class JavaTestGenerator implements SingleTestGenerator { return ClassBodyBuilder.builder(builder, metaData) .field() .messaging() + .customMode() .build() .methodBuilder(methodBuilder); // @formatter:on @@ -112,6 +114,7 @@ public class JavaTestGenerator implements SingleTestGenerator { .spock() .build() .restAssured() + .customMode() .jaxRs() .messaging(); // @formatter:on diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredAcceptor.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredAcceptor.java new file mode 100644 index 0000000000..c2b822fdcf --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredAcceptor.java @@ -0,0 +1,32 @@ +/* + * 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.builder; + +import org.springframework.cloud.contract.verifier.config.TestMode; +import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; + +interface RestAssuredAcceptor { + + default boolean acceptType(GeneratedClassMetaData generatedClassMetaData, + SingleContractMetadata singleContractMetadata) { + return generatedClassMetaData.configProperties.getTestMode() != TestMode.CUSTOM + && generatedClassMetaData.configProperties + .getTestMode() != TestMode.JAXRSCLIENT + && singleContractMetadata.isHttp(); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredCookiesThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredCookiesThen.java index b9aec10638..8c38aba630 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredCookiesThen.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredCookiesThen.java @@ -19,7 +19,8 @@ package org.springframework.cloud.contract.verifier.builder; import org.springframework.cloud.contract.spec.internal.Response; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; -class RestAssuredCookiesThen implements Then, MockMvcAcceptor, CookieElementProcessor { +class RestAssuredCookiesThen + implements Then, RestAssuredAcceptor, CookieElementProcessor { private final BlockBuilder blockBuilder; diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredGiven.java index 3d6d19de8a..ef9982909b 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredGiven.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredGiven.java @@ -20,10 +20,9 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import org.springframework.cloud.contract.verifier.config.TestMode; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; -class RestAssuredGiven implements Given, BodyMethodVisitor { +class RestAssuredGiven implements Given, BodyMethodVisitor, RestAssuredAcceptor { private final BlockBuilder blockBuilder; @@ -33,18 +32,21 @@ class RestAssuredGiven implements Given, BodyMethodVisitor { private final List bodyGivens = new LinkedList<>(); - RestAssuredGiven(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { + RestAssuredGiven(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { this.blockBuilder = blockBuilder; this.generatedClassMetaData = generatedClassMetaData; - this.requestGivens.addAll(Arrays.asList(new MockMvcRequestGiven(blockBuilder, generatedClassMetaData), + this.requestGivens.addAll(Arrays.asList( + new MockMvcRequestGiven(blockBuilder, generatedClassMetaData), new SpockMockMvcRequestGiven(blockBuilder, generatedClassMetaData), new ExplicitRequestGiven(blockBuilder, generatedClassMetaData), new WebTestClientRequestGiven(blockBuilder, generatedClassMetaData))); - this.bodyGivens - .addAll(Arrays.asList(new MockMvcHeadersGiven(blockBuilder), new MockMvcCookiesGiven(blockBuilder), - new MockMvcBodyGiven(blockBuilder, generatedClassMetaData, bodyParser), - new MockMvcMultipartGiven(blockBuilder, generatedClassMetaData, bodyParser), - new SpockMockMvcMultipartGiven(blockBuilder, generatedClassMetaData, bodyParser))); + this.bodyGivens.addAll(Arrays.asList(new MockMvcHeadersGiven(blockBuilder), + new MockMvcCookiesGiven(blockBuilder), + new MockMvcBodyGiven(blockBuilder, generatedClassMetaData, bodyParser), + new JavaMultipartGiven(blockBuilder, generatedClassMetaData, bodyParser), + new SpockMockMvcMultipartGiven(blockBuilder, generatedClassMetaData, + bodyParser))); } @Override @@ -57,15 +59,16 @@ class RestAssuredGiven implements Given, BodyMethodVisitor { } private void addRequestGivenLine(SingleContractMetadata singleContractMetadata) { - this.requestGivens.stream().filter(given -> given.accept(singleContractMetadata)).findFirst().orElseThrow( - () -> new IllegalStateException("No matching request building Given implementation for Rest Assured")) + this.requestGivens.stream().filter(given -> given.accept(singleContractMetadata)) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "No matching request building Given implementation for Rest Assured")) .apply(singleContractMetadata); } @Override public boolean accept(SingleContractMetadata singleContractMetadata) { - return singleContractMetadata.isHttp() - && this.generatedClassMetaData.configProperties.getTestMode() != TestMode.JAXRSCLIENT; + return acceptType(this.generatedClassMetaData, singleContractMetadata); } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredHeadersThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredHeadersThen.java index 6485ad8ac5..659ac65d77 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredHeadersThen.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredHeadersThen.java @@ -26,7 +26,7 @@ import org.springframework.cloud.contract.spec.internal.Response; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; import org.springframework.cloud.contract.verifier.util.MapConverter; -class RestAssuredHeadersThen implements Then, MockMvcAcceptor { +class RestAssuredHeadersThen implements Then, RestAssuredAcceptor { private final BlockBuilder blockBuilder; diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredThen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredThen.java index e0ecf8e7cb..f51095f98f 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredThen.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredThen.java @@ -20,10 +20,9 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import org.springframework.cloud.contract.verifier.config.TestMode; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; -class RestAssuredThen implements Then, BodyMethodVisitor { +class RestAssuredThen implements Then, BodyMethodVisitor, RestAssuredAcceptor { private final BlockBuilder blockBuilder; @@ -31,14 +30,17 @@ class RestAssuredThen implements Then, BodyMethodVisitor { private final List thens = new LinkedList<>(); - RestAssuredThen(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser, + RestAssuredThen(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser, ComparisonBuilder comparisonBuilder) { this.blockBuilder = blockBuilder; this.generatedClassMetaData = generatedClassMetaData; - this.thens.addAll(Arrays.asList(new RestAssuredStatusCodeThen(this.blockBuilder, comparisonBuilder), + this.thens.addAll(Arrays.asList( + new RestAssuredStatusCodeThen(this.blockBuilder, comparisonBuilder), new RestAssuredHeadersThen(this.blockBuilder, comparisonBuilder), new RestAssuredCookiesThen(this.blockBuilder, comparisonBuilder), - new GenericHttpBodyThen(this.blockBuilder, generatedClassMetaData, bodyParser, comparisonBuilder))); + new GenericHttpBodyThen(this.blockBuilder, generatedClassMetaData, + bodyParser, comparisonBuilder))); } @Override @@ -50,8 +52,7 @@ class RestAssuredThen implements Then, BodyMethodVisitor { @Override public boolean accept(SingleContractMetadata singleContractMetadata) { - return singleContractMetadata.isHttp() - && this.generatedClassMetaData.configProperties.getTestMode() != TestMode.JAXRSCLIENT; + return acceptType(this.generatedClassMetaData, singleContractMetadata); } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredWhen.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredWhen.java index 2699cc00e2..77b19ec439 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredWhen.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/RestAssuredWhen.java @@ -20,10 +20,9 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import org.springframework.cloud.contract.verifier.config.TestMode; import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; -class RestAssuredWhen implements When, BodyMethodVisitor { +class RestAssuredWhen implements When, BodyMethodVisitor, RestAssuredAcceptor { private final BlockBuilder blockBuilder; @@ -33,16 +32,21 @@ class RestAssuredWhen implements When, BodyMethodVisitor { private final List whens = new LinkedList<>(); - RestAssuredWhen(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { + RestAssuredWhen(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { this.blockBuilder = blockBuilder; this.generatedClassMetaData = generatedClassMetaData; - this.responseWhens.addAll(Arrays.asList(new MockMvcResponseWhen(blockBuilder, this.generatedClassMetaData), + this.responseWhens.addAll(Arrays.asList( + new MockMvcResponseWhen(blockBuilder, this.generatedClassMetaData), new SpockMockMvcResponseWhen(blockBuilder, this.generatedClassMetaData), new ExplicitResponseWhen(blockBuilder, this.generatedClassMetaData), - new WebTestClientResponseWhen(blockBuilder, this.generatedClassMetaData))); - this.whens.addAll(Arrays.asList(new MockMvcQueryParamsWhen(this.blockBuilder, bodyParser), - new MockMvcAsyncWhen(this.blockBuilder, this.generatedClassMetaData), - new MockMvcUrlWhen(this.blockBuilder, bodyParser))); + new WebTestClientResponseWhen(blockBuilder, + this.generatedClassMetaData))); + this.whens.addAll( + Arrays.asList(new MockMvcQueryParamsWhen(this.blockBuilder, bodyParser), + new MockMvcAsyncWhen(this.blockBuilder, + this.generatedClassMetaData), + new MockMvcUrlWhen(this.blockBuilder, bodyParser))); } @Override @@ -55,15 +59,16 @@ class RestAssuredWhen implements When, BodyMethodVisitor { } private void addResponseWhenLine(SingleContractMetadata singleContractMetadata) { - this.responseWhens.stream().filter(when -> when.accept(singleContractMetadata)).findFirst().orElseThrow( - () -> new IllegalStateException("No matching request building When implementation for Rest Assured")) + this.responseWhens.stream().filter(when -> when.accept(singleContractMetadata)) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "No matching request building When implementation for Rest Assured")) .apply(singleContractMetadata); } @Override public boolean accept(SingleContractMetadata singleContractMetadata) { - return singleContractMetadata.isHttp() - && this.generatedClassMetaData.configProperties.getTestMode() != TestMode.JAXRSCLIENT; + return acceptType(this.generatedClassMetaData, singleContractMetadata); } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SingleMethodBuilder.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SingleMethodBuilder.java index 0bd7e13258..c9321feb89 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SingleMethodBuilder.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SingleMethodBuilder.java @@ -99,6 +99,20 @@ class SingleMethodBuilder { .methodPostProcessor(new TemplateUpdatingMethodPostProcessor(this.blockBuilder)); } + SingleMethodBuilder customMode() { + return given(new CustomModeGiven(this.blockBuilder, this.generatedClassMetaData, + CustomModeBodyParser.INSTANCE)) + .methodPreProcessor(new InProgressContractMethodPreProcessor()) + .when(new CustomModeWhen(this.blockBuilder, + this.generatedClassMetaData)) + .then(new CustomModeThen(this.blockBuilder, + this.generatedClassMetaData, + CustomModeBodyParser.INSTANCE, + ComparisonBuilder.JAVA_HTTP_INSTANCE)) + .methodPostProcessor(new TemplateUpdatingMethodPostProcessor( + this.blockBuilder)); + } + SingleMethodBuilder jaxRs() { return methodPreProcessor(new InProgressContractMethodPreProcessor()) .given(new JaxRsGiven(this.generatedClassMetaData)) diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java index 48f69c30ad..42a40ca4cf 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/SpockMockMvcMultipartGiven.java @@ -25,7 +25,7 @@ import org.springframework.cloud.contract.verifier.file.SingleContractMetadata; import org.springframework.cloud.contract.verifier.util.ContentUtils; import org.springframework.cloud.contract.verifier.util.MapConverter; -class SpockMockMvcMultipartGiven implements Given { +class SpockMockMvcMultipartGiven implements Given, MockMvcAcceptor { private final BlockBuilder blockBuilder; @@ -35,8 +35,8 @@ class SpockMockMvcMultipartGiven implements Given { private final BodyParser bodyParser; - SpockMockMvcMultipartGiven(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData, - BodyParser bodyParser) { + SpockMockMvcMultipartGiven(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData, BodyParser bodyParser) { this.blockBuilder = blockBuilder; this.bodyReader = new BodyReader(generatedClassMetaData); this.bodyParser = bodyParser; @@ -45,40 +45,48 @@ class SpockMockMvcMultipartGiven implements Given { @Override public MethodVisitor apply(SingleContractMetadata metadata) { - getMultipartParameters(metadata).entrySet() - .forEach(entry -> this.blockBuilder.addLine(getMultipartParameterLine(metadata, entry))); + getMultipartParameters(metadata).entrySet().forEach(entry -> this.blockBuilder + .addLine(getMultipartParameterLine(metadata, entry))); return this; } - private String getMultipartParameterLine(SingleContractMetadata metadata, Map.Entry parameter) { + private String getMultipartParameterLine(SingleContractMetadata metadata, + Map.Entry parameter) { if (parameter.getValue() instanceof NamedProperty) { - return ".multiPart(" + getMultipartFileParameterContent(metadata, parameter.getKey(), - (NamedProperty) parameter.getValue()) + ")"; + return ".multiPart(" + getMultipartFileParameterContent(metadata, + parameter.getKey(), (NamedProperty) parameter.getValue()) + ")"; } return getParameterString(parameter); } @SuppressWarnings("unchecked") private Map getMultipartParameters(SingleContractMetadata metadata) { - return (Map) metadata.getContract().getRequest().getMultipart().getServerValue(); + return (Map) metadata.getContract().getRequest().getMultipart() + .getServerValue(); } - private String getMultipartFileParameterContent(SingleContractMetadata metadata, String propertyName, - NamedProperty propertyValue) { - return ContentUtils.getGroovyMultipartFileParameterContent(propertyName, propertyValue, - fileProp -> this.bodyReader.readBytesFromFileString(metadata, fileProp, CommunicationType.REQUEST)); + private String getMultipartFileParameterContent(SingleContractMetadata metadata, + String propertyName, NamedProperty propertyValue) { + return ContentUtils.getGroovyMultipartFileParameterContent(propertyName, + propertyValue, + fileProp -> this.bodyReader.readBytesFromFileString(metadata, fileProp, + CommunicationType.REQUEST)); } private String getParameterString(Map.Entry parameter) { return ".param(" + this.bodyParser.quotedShortText(parameter.getKey()) + ", " - + this.bodyParser.quotedShortText(MapConverter.getTestSideValuesForNonBody(parameter.getValue())) + ")"; + + this.bodyParser.quotedShortText( + MapConverter.getTestSideValuesForNonBody(parameter.getValue())) + + ")"; } @Override public boolean accept(SingleContractMetadata metadata) { Request request = metadata.getContract().getRequest(); return request != null && request.getMultipart() != null - && this.generatedClassMetaData.configProperties.getTestFramework() == TestFramework.SPOCK; + && acceptType(this.generatedClassMetaData, metadata) + && this.generatedClassMetaData.configProperties + .getTestFramework() == TestFramework.SPOCK; } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomImports.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/UserImports.java similarity index 90% rename from spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomImports.java rename to spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/UserImports.java index 00b6cf51b9..4ecea6f427 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomImports.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/UserImports.java @@ -18,13 +18,14 @@ package org.springframework.cloud.contract.verifier.builder; import java.util.Arrays; -class CustomImports implements Imports { +class UserImports implements Imports { private final BlockBuilder blockBuilder; private final GeneratedClassMetaData generatedClassMetaData; - CustomImports(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData) { + UserImports(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData) { this.blockBuilder = blockBuilder; this.generatedClassMetaData = generatedClassMetaData; } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomStaticImports.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/UserStaticImports.java similarity index 84% rename from spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomStaticImports.java rename to spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/UserStaticImports.java index 380346d421..be09b2fd11 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomStaticImports.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/UserStaticImports.java @@ -18,13 +18,14 @@ package org.springframework.cloud.contract.verifier.builder; import java.util.Arrays; -class CustomStaticImports implements Imports { +class UserStaticImports implements Imports { private final BlockBuilder blockBuilder; private final GeneratedClassMetaData generatedClassMetaData; - CustomStaticImports(BlockBuilder blockBuilder, GeneratedClassMetaData generatedClassMetaData) { + UserStaticImports(BlockBuilder blockBuilder, + GeneratedClassMetaData generatedClassMetaData) { this.blockBuilder = blockBuilder; this.generatedClassMetaData = generatedClassMetaData; } @@ -39,7 +40,8 @@ class CustomStaticImports implements Imports { @Override public boolean accept() { return this.generatedClassMetaData.configProperties.getStaticImports() != null - && this.generatedClassMetaData.configProperties.getStaticImports().length > 0; + && this.generatedClassMetaData.configProperties + .getStaticImports().length > 0; } } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/BaseImportProvider.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/BaseImportProvider.java deleted file mode 100644 index caa5edd83b..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/BaseImportProvider.java +++ /dev/null @@ -1,96 +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.builder.imports; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.springframework.cloud.contract.verifier.config.TestFramework.CUSTOM; -import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT; -import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT5; -import static org.springframework.cloud.contract.verifier.config.TestFramework.SPOCK; -import static org.springframework.cloud.contract.verifier.config.TestFramework.TESTNG; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.cloud.contract.verifier.config.TestFramework; - -/** - * Provides imports based on test framework. - * - * @author Olga Maciaszek-Sharma - * @since 2.1.0 - * @deprecated - */ -@Deprecated -public class BaseImportProvider { - - private static final ImportDefinitions GENERAL_IMPORTS = new ImportDefinitions(emptyList(), - Arrays.asList( - "org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat", - "org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*")); - - private static final Map RULE_IMPORT; - - private static final Map TEST_FRAMEWORK_SPECIFIC_IMPORTS; - - static { - TEST_FRAMEWORK_SPECIFIC_IMPORTS = new HashMap<>(5); - TEST_FRAMEWORK_SPECIFIC_IMPORTS.put(JUNIT, new ImportDefinitions(singletonList("org.junit.Test"))); - TEST_FRAMEWORK_SPECIFIC_IMPORTS.put(JUNIT5, new ImportDefinitions(singletonList("org.junit.jupiter.api.Test"))); - TEST_FRAMEWORK_SPECIFIC_IMPORTS.put(SPOCK, new ImportDefinitions(emptyList())); - TEST_FRAMEWORK_SPECIFIC_IMPORTS.put(TESTNG, - new ImportDefinitions(singletonList("org.testng.annotations.Test"))); - TEST_FRAMEWORK_SPECIFIC_IMPORTS.put(CUSTOM, new ImportDefinitions(emptyList())); - - RULE_IMPORT = new HashMap<>(5); - RULE_IMPORT.put(JUNIT, "org.junit.Rule"); - RULE_IMPORT.put(JUNIT5, "org.junit.jupiter.api.extension.ExtendWith"); - RULE_IMPORT.put(SPOCK, "org.junit.Rule"); - RULE_IMPORT.put(TESTNG, "org.junit.Rule"); - RULE_IMPORT.put(CUSTOM, "org.junit.Rule"); - } - - /** - * Returns list of imports for provided test framework. - * @param testFramework - * @return list of imports - */ - public static List getImports(TestFramework testFramework) { - List result = new ArrayList<>(GENERAL_IMPORTS.getImports()); - result.addAll(TEST_FRAMEWORK_SPECIFIC_IMPORTS.get(testFramework).getImports()); - return result; - } - - /** - * @param testFramework test framework to pick the static imports for - * @return list of static imports for provided test framework. - */ - public static List getStaticImports(TestFramework testFramework) { - List result = new ArrayList<>(GENERAL_IMPORTS.getStaticImports()); - result.addAll(TEST_FRAMEWORK_SPECIFIC_IMPORTS.get(testFramework).getStaticImports()); - return result; - } - - public static String getRuleImport(TestFramework testFramework) { - return RULE_IMPORT.get(testFramework); - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/HttpImportProvider.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/HttpImportProvider.java deleted file mode 100644 index 7a22ef3714..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/HttpImportProvider.java +++ /dev/null @@ -1,144 +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.builder.imports; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.springframework.cloud.contract.verifier.config.TestFramework.CUSTOM; -import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT; -import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT5; -import static org.springframework.cloud.contract.verifier.config.TestFramework.SPOCK; -import static org.springframework.cloud.contract.verifier.config.TestFramework.TESTNG; -import static org.springframework.cloud.contract.verifier.config.TestMode.EXPLICIT; -import static org.springframework.cloud.contract.verifier.config.TestMode.JAXRSCLIENT; -import static org.springframework.cloud.contract.verifier.config.TestMode.MOCKMVC; -import static org.springframework.cloud.contract.verifier.config.TestMode.WEBTESTCLIENT; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.tuple.Pair; - -import org.springframework.cloud.contract.verifier.config.TestFramework; -import org.springframework.cloud.contract.verifier.config.TestMode; - -/** - * Provides imports based on test framework and test mode. - * - * @author Olga Maciaszek-Sharma - * @since 2.1.0 - */ -@Deprecated -public class HttpImportProvider { - - private final Map, ImportDefinitions> FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS; - - private final Map TEST_MODE_SPECIFIC_IMPORTS; - - public HttpImportProvider(String restAssuredPackage) { - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS = new HashMap<>(20); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT, JAXRSCLIENT), - new ImportDefinitions(singletonList("javax.ws.rs.core.Response"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT5, JAXRSCLIENT), - new ImportDefinitions(singletonList("javax.ws.rs.core.Response"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(TESTNG, JAXRSCLIENT), - new ImportDefinitions(singletonList("javax.ws.rs.core.Response"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT, MOCKMVC), - new ImportDefinitions( - Arrays.asList(restAssuredPackage + ".module.mockmvc.specification.MockMvcRequestSpecification", - restAssuredPackage + ".response.ResponseOptions"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT, WEBTESTCLIENT), - new ImportDefinitions(Arrays.asList( - "io.restassured.module.webtestclient.specification.WebTestClientRequestSpecification", - "io.restassured.module.webtestclient.response.WebTestClientResponse"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT5, MOCKMVC), - new ImportDefinitions( - Arrays.asList(restAssuredPackage + ".module.mockmvc.specification.MockMvcRequestSpecification", - restAssuredPackage + ".response.ResponseOptions"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT5, WEBTESTCLIENT), - new ImportDefinitions(Arrays.asList( - "io.restassured.module.webtestclient.specification.WebTestClientRequestSpecification", - "io.restassured.module.webtestclient.response.WebTestClientResponse"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(TESTNG, MOCKMVC), - new ImportDefinitions( - Arrays.asList(restAssuredPackage + ".module.mockmvc.specification.MockMvcRequestSpecification", - restAssuredPackage + ".response.ResponseOptions"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(TESTNG, WEBTESTCLIENT), - new ImportDefinitions(Arrays.asList( - "io.restassured.module.webtestclient.specification.WebTestClientRequestSpecification", - "io.restassured.module.webtestclient.response.WebTestClientResponse"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT, EXPLICIT), - new ImportDefinitions(Arrays.asList(restAssuredPackage + ".specification.RequestSpecification", - restAssuredPackage + ".response.Response"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(JUNIT5, EXPLICIT), - new ImportDefinitions(Arrays.asList(restAssuredPackage + ".specification.RequestSpecification", - restAssuredPackage + ".response.Response"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(TESTNG, EXPLICIT), - new ImportDefinitions(Arrays.asList(restAssuredPackage + ".specification.RequestSpecification", - restAssuredPackage + ".response.Response"))); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(SPOCK, JAXRSCLIENT), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(CUSTOM, JAXRSCLIENT), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(SPOCK, MOCKMVC), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(CUSTOM, MOCKMVC), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(SPOCK, EXPLICIT), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(CUSTOM, EXPLICIT), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(SPOCK, WEBTESTCLIENT), new ImportDefinitions(emptyList())); - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.put(Pair.of(CUSTOM, WEBTESTCLIENT), - new ImportDefinitions(emptyList())); - - TEST_MODE_SPECIFIC_IMPORTS = new HashMap<>(4); - TEST_MODE_SPECIFIC_IMPORTS.put(JAXRSCLIENT, - new ImportDefinitions(emptyList(), singletonList("javax.ws.rs.client.Entity.*"))); - TEST_MODE_SPECIFIC_IMPORTS.put(MOCKMVC, new ImportDefinitions(emptyList(), - singletonList(restAssuredPackage + ".module.mockmvc.RestAssuredMockMvc.*"))); - TEST_MODE_SPECIFIC_IMPORTS.put(EXPLICIT, - new ImportDefinitions(emptyList(), singletonList(restAssuredPackage + ".RestAssured.*"))); - TEST_MODE_SPECIFIC_IMPORTS.put(WEBTESTCLIENT, new ImportDefinitions(emptyList(), - singletonList("io.restassured.module.webtestclient.RestAssuredWebTestClient.*"))); - } - - /** - * Returns list of imports for http test contracts for provided test framework and - * test mode. - * @param testFramework - * @param testMode - * @return list of imports - */ - public List getImports(TestFramework testFramework, TestMode testMode) { - List result = new ArrayList<>(TEST_MODE_SPECIFIC_IMPORTS.get(testMode).getImports()); - result.addAll(FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.get(Pair.of(testFramework, testMode)).getImports()); - return result; - } - - /** - * Returns list of static imports for http test contracts for provided test framework - * and test mode. - * @param testFramework - * @param testMode - * @return list of static imports - */ - public List getStaticImports(TestFramework testFramework, TestMode testMode) { - List result = new ArrayList<>(TEST_MODE_SPECIFIC_IMPORTS.get(testMode).getStaticImports()); - result.addAll( - FRAMEWORK_AND_TEST_MODE_SPECIFIC_IMPORTS.get(Pair.of(testFramework, testMode)).getStaticImports()); - return result; - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/ImportDefinitions.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/ImportDefinitions.java deleted file mode 100644 index 6f5c202190..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/ImportDefinitions.java +++ /dev/null @@ -1,50 +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.builder.imports; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Olga Maciaszek-Sharma - * @since 2.1.0 - */ -@Deprecated -class ImportDefinitions { - - private final List imports; - - private final List staticImports; - - public ImportDefinitions(List imports, List staticImports) { - this.imports = imports; - this.staticImports = staticImports; - } - - public ImportDefinitions(List imports) { - this(imports, new ArrayList<>()); - } - - public final List getImports() { - return imports; - } - - public final List getStaticImports() { - return staticImports; - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/MessagingImportProvider.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/MessagingImportProvider.java deleted file mode 100644 index c13ff3df23..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/imports/MessagingImportProvider.java +++ /dev/null @@ -1,50 +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.builder.imports; - -import java.util.Arrays; -import java.util.List; - -/** - * Provides imports based on test framework and test mode. - * - * @author Olga Maciaszek-Sharma - * @since 2.1.0 - */ -@Deprecated -public class MessagingImportProvider { - - /** - * @return list of imports for messaging test contracts. - */ - public static List getImports() { - return Arrays.asList("javax.inject.Inject", - "org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper", - "org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage", - "org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging"); - } - - /** - * @return list of static imports for messaging test contracts. - */ - public static List getStaticImports() { - return Arrays.asList( - "org.springframework.cloud.contract.verifier.messaging.util.ContractVerifierMessagingUtil.headers", - "org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes"); - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/config/TestMode.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/config/TestMode.java index 073869c4e2..6cb03a421f 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/config/TestMode.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/config/TestMode.java @@ -16,6 +16,8 @@ package org.springframework.cloud.contract.verifier.config; +import org.springframework.cloud.contract.verifier.http.HttpVerifier; + /** * Provides different testing modes. * @@ -25,6 +27,10 @@ package org.springframework.cloud.contract.verifier.config; */ public enum TestMode { + /** + * Requires the user to provide an implementation of the {@link HttpVerifier}. + */ + CUSTOM, /** * Uses Spring's MockMvc mode. */ diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Body.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Body.java new file mode 100644 index 0000000000..f4c6a3fde1 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Body.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-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.http; + +import java.nio.charset.Charset; + +/** + * Abstraction over an HTTP body. + * + * Warning! This API is experimental and can change in time. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public class Body { + + final Object body; + + Body(Object body) { + this.body = body; + } + + /** + * @return body as byte array + */ + public byte[] asByteArray() { + if (body instanceof byte[]) { + return (byte[]) this.body; + } + return body.toString().getBytes(); + } + + /** + * @return body as string + */ + public String asString() { + return asString(Charset.defaultCharset()); + } + + /** + * @param charset to encode the body + * @return body as string + */ + public String asString(Charset charset) { + if (body instanceof String) { + return (String) body; + } + else if (body instanceof byte[]) { + return new String((byte[]) body, charset); + } + return body.toString(); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java new file mode 100644 index 0000000000..57e37e813d --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java @@ -0,0 +1,194 @@ +/* + * Copyright 2012-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.http; + +import java.util.Arrays; +import java.util.Map; + +import org.springframework.cloud.contract.verifier.util.MetadataUtil; +import org.springframework.cloud.contract.verifier.util.SpringCloudContractMetadata; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * Metadata representation of the Contract Verifier's HTTP communication. + * + * Warning! This API is experimental and can change in time. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public class ContractVerifierHttpMetadata implements SpringCloudContractMetadata { + + /** + * Metadata entry in the contract. + */ + public static final String METADATA_KEY = "verifierHttp"; + + /** + * Scheme used for HTTP communication. + */ + private Scheme scheme; + + /** + * Protocol used for HTTP communication. + */ + private Protocol protocol; + + @NonNull + public static ContractVerifierHttpMetadata fromMetadata( + Map metadata) { + return MetadataUtil.fromMetadata(metadata, METADATA_KEY, + new ContractVerifierHttpMetadata()); + } + + @Override + public String key() { + return METADATA_KEY; + } + + @Override + public String description() { + return "Metadata entries used by the framework"; + } + + public Scheme getScheme() { + return scheme; + } + + public void setScheme(Scheme scheme) { + this.scheme = scheme; + } + + public void setScheme(String scheme) { + this.scheme = Scheme.fromString(scheme); + } + + public Protocol getProtocol() { + return this.protocol; + } + + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } + + public void setProtocol(String protocol) { + this.protocol = Protocol.fromString(protocol); + } + + /** + * HTTP communication scheme. + */ + public enum Scheme { + + /** + * HTTP scheme. + */ + HTTP, + + /** + * HTTPS scheme. + */ + HTTPS; + + /** + * Builds an enum from string. + */ + @Nullable + public static Scheme fromString(String scheme) { + return Arrays.stream(values()).filter(p -> p.name().equalsIgnoreCase(scheme)) + .findFirst().orElse(null); + } + + } + + /** + * Taken from OKHttp's Protocol. + */ + public enum Protocol { + + /** + * An obsolete plaintext framing that does not use persistent sockets by default. + */ + HTTP_1_0("http/1.0"), + + /** + * A plaintext framing that includes persistent connections. + * + * This version of OkHttp implements [RFC 7230][rfc_7230], and tracks revisions to + * that spec. + * + * [rfc_7230]: https://tools.ietf.org/html/rfc7230 + */ + HTTP_1_1("http/1.1"), + + /** + * The IETF's binary-framed protocol that includes header compression, + * multiplexing multiple requests on the same socket, and server-push. HTTP/1.1 + * semantics are layered on HTTP/2. + * + * HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support + * [CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256], present in Java 8+ and + * Android 5+. Servers that enforce this may send an exception message including + * the string `INADEQUATE_SECURITY`. + */ + HTTP_2("h2"), + + /** + * Cleartext HTTP/2 with no "upgrade" round trip. This option requires the client + * to have prior knowledge that the server supports cleartext HTTP/2. + * + * See also [Starting HTTP/2 with Prior Knowledge][rfc_7540_34]. + * + * [rfc_7540_34]: https://tools.ietf.org/html/rfc7540.section-3.4 + */ + H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"), + + /** + * QUIC (Quick UDP Internet Connection) is a new multiplexed and secure transport + * atop UDP, designed from the ground up and optimized for HTTP/2 semantics. + * HTTP/1.1 semantics are layered on HTTP/2. + * + * QUIC is not natively supported by OkHttp, but provided to allow a theoretical + * interceptor that provides support. + */ + QUIC("quic"); + + private final String protocol; + + Protocol(String protocol) { + this.protocol = protocol; + } + + @Override + public String toString() { + return this.protocol; + } + + /** + * Builds an enum from string. + */ + @Nullable + public static Protocol fromString(String protocol) { + return Arrays.stream(values()) + .filter(p -> p.protocol.equalsIgnoreCase(protocol)).findFirst() + .orElse(null); + } + + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/HttpVerifier.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/HttpVerifier.java new file mode 100644 index 0000000000..391f5bb8d5 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/HttpVerifier.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-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.http; + +/** + * Abstraction over sending and receiving of http messages. + * + * Warning! This API is experimental and can change in time. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public interface HttpVerifier { + + /** + * Sends a request and blocks for the response. + * @param request - HTTP request + * @return HTTP response + */ + Response exchange(Request request); + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java new file mode 100644 index 0000000000..2ff34ec0ae --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java @@ -0,0 +1,149 @@ +/* + * Copyright 2012-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.http; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * {@link HttpVerifier} implementation that uses {@link OkHttpClient}. + * Has an inbuilt support for GRPC. + * + * Warning! This API is experimental and can change in time. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public class OkHttpHttpVerifier implements HttpVerifier { + + private final String url; + + public OkHttpHttpVerifier(String url) { + this.url = url; + } + + @Override + public Response exchange(Request request) { + String requestContentType = request.contentType(); + // TODO: Resolve protocol and scheme from contract? + OkHttpClient client = new OkHttpClient.Builder() + .protocols(protocols(requestContentType)).build(); + okhttp3.Request req = new okhttp3.Request.Builder() + .url(this.url + (request.path().startsWith("/") ? request.path() + : "/" + request.path())) + .method(request.method().name(), requestBody(request, requestContentType)) + .headers(Headers.of(stringTyped(request.headers()))) // TODO: Add cookies + .build(); + try (okhttp3.Response res = client.newCall(req).execute()) { + return response(res); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private List protocols(String requestContentType) { + if (this.url.startsWith("https")) { + return Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1); + } + else if (isGrpc(requestContentType)) { + return Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE); + } + return Collections.singletonList(Protocol.HTTP_1_1); + } + + private Response response(okhttp3.Response res) throws IOException { + byte[] responseBody = responseBody(res); + // String contentType = res.headers().get("Content-Type"); + // TODO: Response body in the test / contract should already be properly encoded + // if (contentType != null && isGrpc(contentType) && responseBody != null) { + // responseBody = grpcResponseBody(responseBody); + // } + return Response.builder().body(responseBody).statusCode(res.code()) + .headers(withSingleHeader(res)) + // TODO: Add cookies + // .cookies(res.headers().values("Set-Cookie").stream().map(s -> + // s.split(";")).flatMap(Arrays::stream).collect(Collectors.toMap(o -> o. + // , e -> e.getValue().get(0), (a,b) -> a, HashMap::new)))) + .build(); + } + + @Nullable + private RequestBody requestBody(Request request, String requestContentType) { + if (request.body() == null) { + return null; + } + byte[] bodyArray = request.body().asByteArray(); + return RequestBody.create(MediaType.parse(requestContentType), bodyArray); + } + + private boolean isGrpc(String contentType) { + return contentType.startsWith("application/grpc"); + } + + // the encoded body should already have proper byte values + // TODO: This should be removed? + private byte[] grpcRequestBody(Request request) { + byte[] bodyArray; // TODO: Add compression support + byte compressedFlag = 0; + byte[] message = request.body().asByteArray(); + byte[] messageLength = ByteBuffer.allocate(4).putInt(message.length).array(); + bodyArray = ByteBuffer.allocate(1 + messageLength.length + message.length) + .put(compressedFlag).put(messageLength).put(message).array(); + return bodyArray; + } + + // TODO: This should be removed? + private byte[] grpcResponseBody(byte[] responseBody) { + // 5 value = 4th index + // 1 for compression, 4 for message size + int actualPayloadSize = responseBody.length - 5; + byte[] destination = new byte[actualPayloadSize]; + System.arraycopy(responseBody, 5, destination, 0, actualPayloadSize); + responseBody = destination; + return responseBody; + } + + @Nullable + private byte[] responseBody(okhttp3.Response res) throws IOException { + return res.body() != null ? res.body().bytes() : null; + } + + private Map withSingleHeader(okhttp3.Response res) { + return res.headers().toMultimap().entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, e -> e.getValue().get(0), (a, b) -> a, HashMap::new)); + } + + private Map stringTyped(Map headers) { + return headers.entrySet().stream().collect( + Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString())); + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java new file mode 100644 index 0000000000..560bb46145 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java @@ -0,0 +1,256 @@ +/* + * Copyright 2012-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.http; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.cloud.contract.spec.internal.HttpMethods; + +/** + * Abstraction over a HTTP request. + * + * Warning! This API is experimental and can change in time. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public class Request { + + private final HttpMethods.HttpMethod method; + + private final String path; + + private final Body body; + + private final Map headers; + + private final Map cookies; + + Request(HttpMethods.HttpMethod method, String path, Body body, + Map headers, Map cookies) { + this.method = method; + this.path = path; + this.body = body; + this.headers = headers == null ? new HashMap<>() : headers; + this.cookies = cookies == null ? new HashMap<>() : cookies; + } + + /** + * @return content type from headers + */ + public String contentType() { + Object value = this.headers.entrySet().stream() + .filter(e -> e.getKey().toLowerCase().equals("content-type")).findFirst() + .orElse(new AbstractMap.SimpleEntry<>("", null)).getValue(); + if (value == null) { + return null; + } + return value.toString(); + } + + /** + * @return HTTP method + */ + public HttpMethods.HttpMethod method() { + return this.method; + } + + /** + * @return HTTP path + */ + public String path() { + return this.path; + } + + /** + * @return request body + */ + public Body body() { + return this.body; + } + + /** + * @return request headers + */ + public Map headers() { + return this.headers; + } + + /** + * @return request cookies + */ + public Map cookies() { + return this.cookies; + } + + /** + * Factory method to pick the HTTP method. + * @return method builder + */ + public static Request.MethodBuilder given() { + return new Request.MethodBuilder(); + } + + /** + * Builder over HTTP methods. + */ + public static class MethodBuilder { + + /** + * Factory method for DELETE HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder delete(String path) { + return new Request.Builder(HttpMethods.HttpMethod.DELETE, path); + } + + /** + * Factory method for GET HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder get(String path) { + return new Request.Builder(HttpMethods.HttpMethod.GET, path); + } + + /** + * Factory method for HEAD HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder head(String path) { + return new Request.Builder(HttpMethods.HttpMethod.HEAD, path); + } + + /** + * Factory method for OPTIONS HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder options(String path) { + return new Request.Builder(HttpMethods.HttpMethod.OPTIONS, path); + } + + /** + * Factory method for PATCH HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder patch(String path) { + return new Request.Builder(HttpMethods.HttpMethod.PATCH, path); + } + + /** + * Factory method for POST HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder post(String path) { + return new Request.Builder(HttpMethods.HttpMethod.POST, path); + } + + /** + * Factory method for PUT HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder put(String path) { + return new Request.Builder(HttpMethods.HttpMethod.PUT, path); + } + + /** + * Factory method for TRACE HTTP method. + * @param path to call + * @return builder + */ + public Request.Builder trace(String path) { + return new Request.Builder(HttpMethods.HttpMethod.TRACE, path); + } + + } + + /** + * Builder for a {@link Request}. + */ + public static class Builder { + + final HttpMethods.HttpMethod method; + + final String path; + + Body body; + + Map headers = new HashMap<>(); + + Map cookies = new HashMap<>(); + + Builder(HttpMethods.HttpMethod method, String path) { + this.method = method; + this.path = path; + } + + /** + * @param body HTTP body + * @return builder + */ + public Request.Builder body(Object body) { + this.body = new Body(body); + return this; + } + + /** + * @param headers HTTP headers + * @return builder + */ + public Request.Builder headers(Map headers) { + this.headers = headers; + return this; + } + + /** + * @param key HTTP key + * @param value HTTP value + * @return builder + */ + public Request.Builder header(String key, Object value) { + this.headers.put(key, value); + return this; + } + + /** + * @param cookies HTTP cookies + * @return builder + */ + public Request.Builder cookies(Map cookies) { + this.cookies = cookies; + return this; + } + + /** + * @return built {@link Request} + */ + public Request build() { + return new Request(this.method, this.path, this.body, this.headers, + this.cookies); + } + + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Response.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Response.java new file mode 100644 index 0000000000..d368b45497 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Response.java @@ -0,0 +1,171 @@ +/* + * Copyright 2012-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.http; + +import java.util.HashMap; +import java.util.Map; + +/** + * Abstraction over a HTTP response. + * + * Warning! This API is experimental and can change in time. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public class Response { + + private final int statusCode; + + private final Body body; + + private final Map headers; + + private final Map cookies; + + Response(int statusCode, Body body, Map headers, + Map cookies) { + this.statusCode = statusCode; + this.body = body; + this.headers = headers; + this.cookies = cookies; + } + + /** + * @return numerical representation of a status code + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @param key header key + * @return header value or null if not present + */ + public String header(String key) { + return this.headers.entrySet().stream() + .filter(e -> e.getKey().equalsIgnoreCase(key)).findFirst() + .map(e -> e.getValue().toString()).orElse(null); + } + + /** + * @param key cookie key + * @return header value or null if not present + */ + public String cookie(String key) { + return this.cookies.entrySet().stream() + .filter(e -> e.getKey().equalsIgnoreCase(key)).findFirst() + .map(e -> e.getValue().toString()).orElse(null); + } + + /** + * @return response body + */ + public Body getBody() { + return this.body; + } + + /** + * @return builder + */ + public static Response.Builder builder() { + return new Response.Builder(); + } + + /** + * @return headers + */ + public Map headers() { + return this.headers; + } + + /** + * @return cookies + */ + public Map cookies() { + return this.cookies; + } + + /** + * Response builder. + */ + public static class Builder { + + int statusCode; + + Body body; + + Map headers = new HashMap<>(); + + Map cookies = new HashMap<>(); + + /** + * @param status as int + * @return builder + */ + public Response.Builder statusCode(int status) { + this.statusCode = status; + return this; + } + + /** + * @param body - response body + * @return builder + */ + public Response.Builder body(Object body) { + this.body = new Body(body); + return this; + } + + /** + * @param headers - response headers + * @return builder + */ + public Response.Builder headers(Map headers) { + this.headers = headers; + return this; + } + + /** + * @param key header key + * @param value header value + * @return builder + */ + public Response.Builder header(String key, Object value) { + this.headers.put(key, value); + return this; + } + + /** + * @param cookies - response cookies + * @return builder + */ + public Response.Builder cookies(Map cookies) { + this.cookies = cookies; + return this; + } + + /** + * @return response + */ + public Response build() { + return new Response(this.statusCode, this.body, this.headers, this.cookies); + } + + } + +} diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy index 7007324aee..61a42632c4 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/YamlMockMvcMethodBodyBuilderSpec.groovy @@ -122,6 +122,7 @@ response: methodBuilderName | methodBuilder "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue("#187") @@ -155,6 +156,7 @@ response: methodBuilderName | methodBuilder "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('#79') @@ -190,6 +192,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('#79') @@ -228,6 +231,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('#82') @@ -263,6 +267,9 @@ response: "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | '.body("{\\"items\\":[\\"HOP\\"]}")' + "custom" | { + properties.testMode = TestMode.CUSTOM + } | '.body("{\\"items\\":[\\"HOP\\"]}")' } @Issue('#88') @@ -292,6 +299,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } | """.body('''property1=VAL1''')""" "mockmvc" | { properties.testMode = TestMode.MOCKMVC } | '.body("property1=VAL1")' "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | '.body("property1=VAL1")' + "custom" | { properties.testMode = TestMode.CUSTOM } | '.body("property1=VAL1")' } @Issue('185') @@ -326,6 +334,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } def 'should generate assertions for array in response body with #methodBuilderName'() { @@ -363,6 +372,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } def 'should generate assertions for array inside response body element with #methodBuilderName'() { @@ -399,6 +409,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } def 'should generate assertions for nested objects in response body with #methodBuilderName'() { @@ -432,6 +443,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } def 'should generate regex assertions for map objects in response body with #methodBuilderName'() { @@ -469,6 +481,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } def "should generate a call with an url path and query parameters with #methodBuilderName"() { @@ -642,6 +655,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } | ".body('''''')" "mockmvc" | { properties.testMode = TestMode.MOCKMVC } | '.body("")' "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | '.body("")' + "custom" | { properties.testMode = TestMode.CUSTOM } | '.body("")' } def 'should generate test for String in response body with #methodBuilderName'() { @@ -678,6 +692,9 @@ response: "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | 'String responseBody = response.getBody().asString();' | 'assertThat(responseBody).isEqualTo("test");' + "custom" | { + properties.testMode = TestMode.CUSTOM + } | 'String responseBody = response.getBody().asString();' | 'assertThat(responseBody).isEqualTo("test");' } @Issue('113') @@ -731,6 +748,9 @@ response: "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | 'assertThat(response.header("Location")).matches("http://localhost/partners/[0-9]+/users/[0-9]+");' + "custom" | { + properties.testMode = TestMode.CUSTOM + } | 'assertThat(response.header("Location")).matches("http://localhost/partners/[0-9]+/users/[0-9]+");' } def 'should work with more complex stuff and jsonpaths with #methodBuilderName'() { @@ -777,6 +797,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('72') @@ -822,6 +843,9 @@ response: "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | ['''assertThatRejectionReasonIsNull(parsedJson.read("$.rejectionReason"))''', '''assertThatLocationIsNull(response.header("Location"))'''] + "custom" | { + properties.testMode = TestMode.CUSTOM + } | ['''assertThatRejectionReasonIsNull(parsedJson.read("$.rejectionReason"))''', '''assertThatLocationIsNull(response.header("Location"))'''] } def "shouldn't generate unicode escape characters with #methodBuilderName"() { @@ -871,6 +895,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('180') @@ -1188,6 +1213,7 @@ response: methodBuilderName | methodBuilder "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('262') @@ -1261,6 +1287,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('266') @@ -1298,6 +1325,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('266') @@ -1330,6 +1358,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('47') @@ -1420,6 +1449,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } "mockmvc" | { properties.testMode = TestMode.MOCKMVC } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('#273') @@ -1662,6 +1692,9 @@ response: "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | { String s -> "response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application.vnd.fraud.v1.json.*')" } + "custom" | { + properties.testMode = TestMode.CUSTOM + } | { String s -> "response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application.vnd.fraud.v1.json.*')" } } @Issue('#172') @@ -1699,6 +1732,7 @@ response: "spock" | { properties.testFramework = TestFramework.SPOCK } | """responseBody == '''{"a":1}\\n{"a":2}\\n'''""" "mockmvc" | { properties.testMode = TestMode.MOCKMVC } | '''assertThat(responseBody).isEqualTo("{\\"a\\":1}\\n{\\"a\\":2}\\n''' "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | '''assertThat(responseBody).isEqualTo("{\\"a\\":1}\\n{\\"a\\":2}\\n''' + "custom" | { properties.testMode = TestMode.CUSTOM } | '''assertThat(responseBody).isEqualTo("{\\"a\\":1}\\n{\\"a\\":2}\\n''' } @Issue('#443') @@ -1747,6 +1781,9 @@ response: "jaxrs" | { properties.testFramework = TestFramework.JUNIT; properties.testMode = TestMode.JAXRSCLIENT } | { String testContents -> testContents.contains("""assertThat(responseBody).matches("true|false");""") } + "custom" | { + properties.testMode = TestMode.CUSTOM + } | { String testContents -> testContents.contains("""assertThat(responseBody).matches("true|false");""") } } @Issue('#169') @@ -1785,6 +1822,7 @@ response: methodBuilderName | methodBuilder | expectedAssertion "spock" | { properties.testFramework = TestFramework.SPOCK } | '''responseBody == "{\\"a\\":1}\\n{\\"a\\":2}"''' "mockmvc" | { properties.testMode = TestMode.MOCKMVC } | '''assertThat(responseBody).isEqualTo("{\\"a\\":1}\\n{\\"a\\":2}''' + "custom" | { properties.testMode = TestMode.CUSTOM } | '''assertThat(responseBody).isEqualTo("{\\"a\\":1}\\n{\\"a\\":2}''' } @Issue('#169') @@ -1825,6 +1863,9 @@ response: "jaxrs" | { properties.testFramework = TestFramework.JUNIT; properties.testMode = TestMode.JAXRSCLIENT } + "custom" | { + properties.testMode = TestMode.CUSTOM + } } @Issue('#203') @@ -1866,6 +1907,7 @@ response: properties.testFramework = TestFramework.JUNIT; properties.testMode = TestMode.JAXRSCLIENT } "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } + "custom" | { properties.testMode = TestMode.CUSTOM } } @Issue('#226') @@ -1909,5 +1951,8 @@ response: "webclient" | { properties.testMode = TestMode.WEBTESTCLIENT } | { String body -> body.contains('body("12000")') } | { String body -> body.contains('assertThat(responseBody).isEqualTo("12000");') } + "custom" | { + properties.testMode = TestMode.CUSTOM + } | { String body -> body.contains('body("12000")') } | { String body -> body.contains('assertThat(responseBody).isEqualTo("12000");') } } } diff --git a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/dsl/wiremock/SpringCloudContractRequestMatcherTests.java b/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/dsl/wiremock/SpringCloudContractRequestMatcherTests.java index 3151fd723b..d9837add2a 100644 --- a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/dsl/wiremock/SpringCloudContractRequestMatcherTests.java +++ b/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/dsl/wiremock/SpringCloudContractRequestMatcherTests.java @@ -46,7 +46,8 @@ class SpringCloudContractRequestMatcherTests { } }; - MatchResult result = matcher.match(BDDMockito.mock(Request.class), Parameters.one("tool", "foo")); + MatchResult result = matcher.match(BDDMockito.mock(Request.class), + Parameters.one("tool", "foo")); BDDAssertions.then(result.isExactMatch()).isFalse(); } @@ -63,7 +64,8 @@ class SpringCloudContractRequestMatcherTests { } }; - MatchResult result = matcher.match(BDDMockito.mock(Request.class), Parameters.one("contract", "value")); + MatchResult result = matcher.match(BDDMockito.mock(Request.class), + Parameters.one("contract", "value")); BDDAssertions.then(result.isExactMatch()).isFalse(); } @@ -80,22 +82,28 @@ class SpringCloudContractRequestMatcherTests { } }; - MatchResult result = matcher.match(BDDMockito.mock(Request.class), Parameters.one("contract", "value")); + MatchResult result = matcher.match(BDDMockito.mock(Request.class), + Parameters.one("contract", "value")); BDDAssertions.then(result.isExactMatch()).isFalse(); } - private static final String PROPER_YAML = "---\n" + "request:\n" + " method: \"POST\"\n" + " url: \"/graphql\"\n" - + " headers:\n" + " Content-Type: \"application/json\"\n" + " body:\n" + private static final String PROPER_YAML = "---\n" + "request:\n" + + " method: \"POST\"\n" + " url: \"/graphql\"\n" + " headers:\n" + + " Content-Type: \"application/json\"\n" + " body:\n" + " query: \"query queryName($personName: String!) { personToCheck(name: $personName)" - + " { name age } }\"\n" + " variables:\n" + " personName: \"Old Enough\"\n" - + " operationName: \"queryName\"\n" + " matchers:\n" + " headers:\n" - + " - key: \"Content-Type\"\n" + " regex: \"application/json.*\"\n" - + " regexType: \"as_string\"\n" + "response:\n" + " status: 200\n" + " headers:\n" - + " Content-Type: \"application/json\"\n" + " body:\n" + " data:\n" + " personToCheck:\n" - + " name: \"Old Enough\"\n" + " age: \"40\"\n" + " matchers:\n" + " headers:\n" - + " - key: \"Content-Type\"\n" + " regex: \"application/json.*\"\n" - + " regexType: \"as_string\"\n" + "name: \"shouldRetrieveOldEnoughPerson\"\n" + "metadata:\n" + + " { name age } }\"\n" + " variables:\n" + + " personName: \"Old Enough\"\n" + " operationName: \"queryName\"\n" + + " matchers:\n" + " headers:\n" + " - key: \"Content-Type\"\n" + + " regex: \"application/json.*\"\n" + + " regexType: \"as_string\"\n" + "response:\n" + " status: 200\n" + + " headers:\n" + " Content-Type: \"application/json\"\n" + " body:\n" + + " data:\n" + " personToCheck:\n" + " name: \"Old Enough\"\n" + + " age: \"40\"\n" + " matchers:\n" + " headers:\n" + + " - key: \"Content-Type\"\n" + + " regex: \"application/json.*\"\n" + + " regexType: \"as_string\"\n" + + "name: \"shouldRetrieveOldEnoughPerson\"\n" + "metadata:\n" + " verifier:\n" + " tool: \"graphql\"\n"; @Test @@ -109,8 +117,8 @@ class SpringCloudContractRequestMatcherTests { } }; - MatchResult result = matcher.match(BDDMockito.mock(Request.class), - toMap(Tuples.of("tool", "unsupported"), Tuples.of("contract", PROPER_YAML))); + MatchResult result = matcher.match(BDDMockito.mock(Request.class), toMap( + Tuples.of("tool", "unsupported"), Tuples.of("contract", PROPER_YAML))); BDDAssertions.then(result.isExactMatch()).isFalse(); } @@ -145,7 +153,8 @@ class SpringCloudContractRequestMatcherTests { class ApplicableRequestMatcher implements RequestMatcher { @Override - public MatchResult match(List contracts, Request request, Parameters parameters) { + public MatchResult match(List contracts, Request request, + Parameters parameters) { return MatchResult.of(true); }