diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/PactMetaData.java b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/PactMetaData.java new file mode 100644 index 0000000000..8930cdcfb5 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/PactMetaData.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.verifier.spec.pact; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import au.com.dius.pact.core.model.ProviderState; + +import org.springframework.cloud.contract.verifier.util.MetadataUtil; +import org.springframework.cloud.contract.verifier.util.SpringCloudContractMetadata; +import org.springframework.lang.NonNull; + +public class PactMetaData implements SpringCloudContractMetadata { + + /** + * Key under which this metadata entry can be found in contract's metadata. + */ + public static final String METADATA_KEY = "pact"; + + /** + * Metadata for adding {@link ProviderState} in Pact contract. + */ + private List providerStates = new ArrayList<>(); + + public List getProviderStates() { + return this.providerStates; + } + + public void setProviderStates(List providerStates) { + this.providerStates = providerStates; + } + + @NonNull + public static PactMetaData fromMetadata(Map metadata) { + return MetadataUtil.fromMetadata(metadata, PactMetaData.METADATA_KEY, new PactMetaData()); + } + + @Override + public String key() { + return METADATA_KEY; + } + + @Override + public String description() { + return "Metadata for converting Contract to Pact"; + } + + /** + * {@link ProviderState} metadata. + */ + public static class ProviderStateMetadata { + + /** + * If set, will be added to PACT provider states. + */ + private String name; + + private Map params; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getParams() { + return this.params; + } + + public void setParams(Map params) { + this.params = params; + } + + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/RequestResponsePactCreator.java b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/RequestResponsePactCreator.java index b77a157c70..a386f99fd7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/RequestResponsePactCreator.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/main/java/org/springframework/cloud/contract/verifier/spec/pact/RequestResponsePactCreator.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -30,6 +31,7 @@ import au.com.dius.pact.consumer.dsl.DslPart; import au.com.dius.pact.consumer.dsl.PactDslRequestWithPath; import au.com.dius.pact.consumer.dsl.PactDslResponse; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.dsl.PactDslWithState; import au.com.dius.pact.core.model.RequestResponsePact; import org.apache.commons.lang3.StringUtils; @@ -107,14 +109,10 @@ class RequestResponsePactCreator { } private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslResponse pactDslResponse) { + PactDslRequestWithPath pactDslRequest = getPactDslRequest(contract, + getPactDslWithStateFunction(pactDslResponse), + getPactDslRequestWithPathBiFunction(pactDslResponse)); Request request = contract.getRequest(); - PactDslRequestWithPath pactDslRequest = pactDslResponse - .uponReceiving(StringUtils.isNotBlank(contract.getDescription()) ? contract.getDescription() : "") - .path(url(request)).method(request.getMethod().getServerValue().toString()); - String query = query(request); - if (StringUtils.isNotBlank(query)) { - pactDslRequest = pactDslRequest.encodedQuery(query); - } final PactDslRequestWithPath finalPactDslRequest = pactDslRequest; if (request.getHeaders() != null) { request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h)); @@ -134,23 +132,27 @@ class RequestResponsePactCreator { return pactDslRequest; } + private Function getPactDslWithStateFunction( + PactDslResponse pactDslResponse) { + return stateMetadata -> pactDslResponse.given(stateMetadata.getName(), stateMetadata.getParams()); + } + + private BiFunction getPactDslRequestWithPathBiFunction( + PactDslResponse pactDslResponse) { + return (description, request) -> pactDslResponse.uponReceiving(description).path(url(request)) + .method(request.getMethod().getServerValue().toString()); + } + private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslWithProvider pactDslWithProvider) { + PactDslRequestWithPath pactDslRequest = getPactDslRequest(contract, + getPactDslWithStateFunction(pactDslWithProvider), + getPactDslRequestWithPathBiFunction(pactDslWithProvider)); Request request = contract.getRequest(); - PactDslRequestWithPath pactDslRequest = pactDslWithProvider - .uponReceiving(StringUtils.isNotBlank(contract.getDescription()) ? contract.getDescription() : "") - .path(url(request)).method(request.getMethod().getServerValue().toString()); - String query = query(request); - if (StringUtils.isNotBlank(query)) { - pactDslRequest = pactDslRequest.encodedQuery(query); - } final PactDslRequestWithPath finalPactDslRequest = pactDslRequest; if (request.getHeaders() != null) { - request.getHeaders().getEntries().forEach(h -> { - processHeader(finalPactDslRequest, h); - }); + request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h)); } - if (request.getBody() != null) { DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getServerValue); if (request.getBodyMatchers() != null) { @@ -163,6 +165,57 @@ class RequestResponsePactCreator { return pactDslRequest; } + private Function getPactDslWithStateFunction( + PactDslWithProvider pactDslWithProvider) { + return stateMetadata -> pactDslWithProvider.given(stateMetadata.getName(), stateMetadata.getParams()); + } + + private BiFunction getPactDslRequestWithPathBiFunction( + PactDslWithProvider pactDslWithProvider) { + return (description, request) -> pactDslWithProvider.uponReceiving(description).path(url(request)) + .method(request.getMethod().getServerValue().toString()); + } + + private PactDslRequestWithPath getPactDslRequest(Contract contract, + Function pactDslWithStateFunction, + BiFunction pactDslRequestWithPathBiFunction) { + PactDslWithState pactDslWithState = getPactDslWithState(contract, pactDslWithStateFunction); + String description = StringUtils.isNotBlank(contract.getDescription()) ? contract.getDescription() : ""; + Request request = contract.getRequest(); + PactDslRequestWithPath pactDslRequest; + if (pactDslWithState != null) { + pactDslRequest = pactDslWithState.uponReceiving(description).path(url(request)) + .method(request.getMethod().getServerValue().toString()); + } + else { + pactDslRequest = pactDslRequestWithPathBiFunction.apply(description, request); + } + String query = query(request); + if (StringUtils.isNotBlank(query)) { + pactDslRequest = pactDslRequest.encodedQuery(query); + } + return pactDslRequest; + } + + private PactDslWithState getPactDslWithState(Contract contract, + Function pactDslWithStateFunction) { + PactDslWithState pactDslWithState = null; + if (contract.getMetadata().containsKey(PactMetaData.METADATA_KEY)) { + PactMetaData metadata = PactMetaData.fromMetadata(contract.getMetadata()); + if (!metadata.getProviderStates().isEmpty()) { + for (PactMetaData.ProviderStateMetadata stateMetadata : metadata.getProviderStates()) { + if (pactDslWithState == null) { + pactDslWithState = pactDslWithStateFunction.apply(stateMetadata); + } + else { + pactDslWithState = pactDslWithState.given(stateMetadata.getName(), stateMetadata.getParams()); + } + } + } + } + return pactDslWithState; + } + private String url(Request request) { if (request.getUrlPath() != null) { return request.getUrlPath().getServerValue().toString(); diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactMetaDataTests.java b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactMetaDataTests.java new file mode 100644 index 0000000000..21ffe70ab8 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactMetaDataTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020-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.spec.pact; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +class PactMetaDataTests { + + YAMLMapper mapper = new YAMLMapper(); + + @Test + void should_parse_the_metadata_entry() throws JsonProcessingException { + // @formatter:off + String yamlEntry = "" + + "pact:\n" + + " providerStates:\n" + + " - name: state1\n" + + " params:\n" + + " id: 1\n" + + " value: value1\n" + + " - name: state2\n" + + " params:\n" + + " id: 2\n" + + " value: value2"; + // @formatter:on + + PactMetaData metadata = PactMetaData + .fromMetadata(this.mapper.readerForMapOf(Object.class).readValue(yamlEntry)); + + String serialized = this.mapper.writer().forType(PactMetaData.class).writeValueAsString(metadata); + BDDAssertions.then(serialized).isEqualToNormalizingPunctuationAndWhitespace(yamlEntry.replace("pact:\n", "")); + } + +} \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.groovy b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.groovy index f1e5fa92e5..3a599d93b4 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.groovy @@ -56,4 +56,25 @@ org.springframework.cloud.contract.spec.Contract.make { contentType('application/json') } } + metadata([ + pact: [ + providerStates: + [ + [ + name: "someState1", + params: [ + id: 1, + value: "someValue1" + ] + ], + [ + name: "someState2", + params: [ + id: 2, + value: "someValue2" + ] + ] + ] + ] + ]) } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.json b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.json index b79b029468..3bc7782717 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.json +++ b/spring-cloud-contract-tools/spring-cloud-contract-pact/src/test/resources/contracts/shouldConvertAllGenerators.json @@ -266,7 +266,23 @@ } } } - } + }, + "providerStates": [ + { + "name": "someState1", + "params": { + "id": 1, + "value": "someValue1" + } + }, + { + "name": "someState2", + "params": { + "id": 2, + "value": "someValue2" + } + } + ] } ], "metadata": {