Removing the Pact integration; fixes gh-1845

This commit is contained in:
Marcin Grzejszczak
2022-11-17 16:01:40 +01:00
parent cff332fc40
commit dc581e8934
66 changed files with 4 additions and 6689 deletions

View File

@@ -19,7 +19,6 @@
<modules>
<module>spring-cloud-contract-converters</module>
<module>spring-cloud-contract-pact</module>
<module>spring-cloud-contract-maven-plugin</module>
<module>spring-cloud-contract-gradle-plugin</module>
<module>spring-cloud-contract-gradle-portal-plugin</module>

View File

@@ -378,11 +378,6 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-testing</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>

View File

@@ -22,8 +22,6 @@ import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.util.StringUtils;
import static java.nio.charset.Charset.defaultCharset;
import static org.apache.commons.io.FileUtils.readFileToString;
import static org.apache.maven.plugin.testing.MojoParameters.newParameter;
@@ -309,21 +307,6 @@ public class PluginUnitTest extends AbstractMojoTest {
"target/stubs/META-INF/org.springframework.cloud.verifier.sample/common-repo/0.1/contracts/pom.xml");
}
@Test
public void shouldGenerateContractTestsForPactAndMaintainIndents() throws Exception {
File basedir = getBasedir("pact");
executeMojo(basedir, "generateTests", defaultPackageForTests());
assertFilesPresent(basedir,
"target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java");
File test = new File(basedir,
"target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java");
String testContents = readFileToString(test, defaultCharset());
int countOccurrencesOf = StringUtils.countOccurrencesOf(testContents, "\t\tMockMvcRequestSpecification");
then(countOccurrencesOf).isEqualTo(4);
}
@Test
public void shouldRunPushStubsToScm() throws Exception {
File basedir = getBasedir("git-basic-remote-contracts");

View File

@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud.verifier.sample</groupId>
<artifactId>sample-pact-project</artifactId>
<version>0.1</version>
<properties>
<spring.cloud.contract.version>2.0.0.BUILD-SNAPSHOT
</spring.cloud.contract.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<baseClassForTests>com.example.FooBase</baseClassForTests>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*com.*</contractPackageRegex>
<baseClassFQN>com.example.TestBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<version>${spring.cloud.contract.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,42 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "PUT",
"path": "/fraudcheck",
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"clientId": "1234567890",
"loanAmount": 99999
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
},
"body": {
"fraudCheckStatus": "FRAUD",
"rejectionReason": "Amount too high"
}
}
}
],
"metadata": {
"pact-specification": {
"version": "2.0.0"
},
"pact-jvm": {
"version": "2.4.18"
}
}
}

View File

@@ -1,42 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "PUT",
"path": "/fraudcheck",
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"clientId": "1234567890",
"loanAmount": 123.123
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
},
"body": {
"fraudCheckStatus": "OK",
"rejectionReason": null
}
}
}
],
"metadata": {
"pact-specification": {
"version": "2.0.0"
},
"pact-jvm": {
"version": "2.4.18"
}
}
}

View File

@@ -1,34 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "GET",
"path": "/drunks"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
},
"body": {
"count": 100
}
}
}
],
"metadata": {
"pact-specification": {
"version": "2.0.0"
},
"pact-jvm": {
"version": "2.4.18"
}
}
}

View File

@@ -1,34 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "GET",
"path": "/frauds"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
},
"body": {
"count": 200
}
}
}
],
"metadata": {
"pact-specification": {
"version": "2.0.0"
},
"pact-jvm": {
"version": "2.4.18"
}
}
}

View File

@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-tools</artifactId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-pact</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Contract Pact</name>
<description>Spring Cloud Contract Pact</description>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-converters</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-nio</artifactId>
</dependency>
<dependency>
<groupId>au.com.dius.pact.consumer</groupId>
<artifactId>junit5</artifactId>
</dependency>
<dependency>
<groupId>au.com.dius.pact.provider</groupId>
<artifactId>junit5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>compileTests</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,467 +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.stubrunner;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import au.com.dius.pact.core.model.Pact;
import au.com.dius.pact.core.model.PactSpecVersion;
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver;
import au.com.dius.pact.core.support.expressions.ValueResolver;
import au.com.dius.pact.provider.junitsupport.loader.PactBroker;
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerAuth;
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerLoader;
import au.com.dius.pact.provider.junitsupport.loader.PactLoader;
import au.com.dius.pact.provider.junitsupport.loader.VersionSelector;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Allows downloading of Pact files from the Pact Broker.
*
* @author Marcin Grzejszczak
* @author Tim Ysewyn
* @since 2.0.0
*/
public final class PactStubDownloaderBuilder implements StubDownloaderBuilder {
private static final List<String> ACCEPTABLE_PROTOCOLS = Collections.singletonList("pact");
/**
* Does any of the accepted protocols matches the URL of the repository.
* @param url - of the repository
* @return {@code true} if the protocol is accepted
*/
private static boolean isProtocolAccepted(String url) {
return ACCEPTABLE_PROTOCOLS.stream().anyMatch(url::startsWith);
}
@Override
public StubDownloader build(StubRunnerOptions stubRunnerOptions) {
if (stubRunnerOptions.getStubsMode() == StubRunnerProperties.StubsMode.CLASSPATH
|| stubRunnerOptions.getStubRepositoryRoot() == null) {
return null;
}
Resource resource = stubRunnerOptions.getStubRepositoryRoot();
if (!(resource instanceof PactResource)) {
return null;
}
return new PactStubDownloader(stubRunnerOptions);
}
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (!StringUtils.hasText(location) || !isProtocolAccepted(location)) {
return null;
}
return new PactResource(location);
}
}
class PactResource extends AbstractResource {
private final String rawLocation;
PactResource(String location) {
this.rawLocation = location;
}
@Override
public String getDescription() {
return this.rawLocation;
}
@Override
public InputStream getInputStream() {
return null;
}
@Override
public URI getURI() {
return URI.create(this.rawLocation);
}
}
class PactStubDownloader implements StubDownloader {
private static final String TEMP_DIR_PREFIX = "pact";
private static final Log log = LogFactory.getLog(PactStubDownloader.class);
// Preloading class for the shutdown hook not to throw ClassNotFound
private static final Class CLAZZ = TemporaryFileStorage.class;
private static final String ARTIFICIAL_NAME_ENDING_WITH_GROOVY = "name.groovy";
private static final String PROVIDER_NAME_WITH_GROUP_ID = "pactbroker.provider-name-with-group-id";
private final StubRunnerOptions stubRunnerOptions;
private final boolean deleteStubsAfterTest;
private final ObjectMapper objectMapper;
PactStubDownloader(StubRunnerOptions stubRunnerOptions) {
this.stubRunnerOptions = stubRunnerOptions;
this.objectMapper = new ObjectMapper();
this.deleteStubsAfterTest = stubRunnerOptions.isDeleteStubsAfterTest();
registerShutdownHook();
}
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(StubConfiguration stubConfiguration) {
String version = stubConfiguration.version;
final FromPropsThenFromSysEnv resolver = new FromPropsThenFromSysEnv(this.stubRunnerOptions);
List<String> tags = tags(version, resolver);
try {
PactLoader loader = pactBrokerLoader(resolver, tags);
String providerName = providerName(stubConfiguration);
List<Pact> pacts = loader.load(providerName);
if (pacts.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No pact definitions found for provider [" + providerName + "]");
}
return null;
}
File tmpDirWhereStubsWillBeUnzipped = TemporaryFileStorage.createTempDir(TEMP_DIR_PREFIX);
// make the groupid / artifactid folders
String coordinatesFolderName = stubConfiguration.getGroupId().replace(".", File.separator) + File.separator
+ stubConfiguration.getArtifactId();
File contractsFolder = new File(tmpDirWhereStubsWillBeUnzipped,
coordinatesFolderName + File.separator + "contracts");
File mappingsFolder = new File(tmpDirWhereStubsWillBeUnzipped,
coordinatesFolderName + File.separator + "mappings");
boolean createdContractsDirs = contractsFolder.mkdirs();
boolean createdMappingsDirs = mappingsFolder.mkdirs();
if (!createdContractsDirs || !createdMappingsDirs) {
throw new IllegalStateException("Failed to create mandatory [contracts] or [mappings] folders under ["
+ coordinatesFolderName + "]");
}
storePacts(providerName, pacts, contractsFolder, mappingsFolder);
return new AbstractMap.SimpleEntry<>(stubConfiguration, tmpDirWhereStubsWillBeUnzipped);
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
private void storePacts(String providerName, List<Pact> pacts, File contractsFolder, File mappingsFolder) {
for (int i = 0; i < pacts.size(); i++) {
String json = toJson(pacts.get(i).toMap(PactSpecVersion.V3));
File file = new File(contractsFolder, i + "_" + providerName.replace(":", "_") + "_pact.json");
storeFile(file.toPath(), json.getBytes());
try {
storeMapping(mappingsFolder, file);
}
catch (Exception e) {
log.warn("Exception occurred while trying to store the mapping", e);
}
}
}
private void storeMapping(File mappingsFolder, File file) {
Collection<Contract> contracts = new PactContractConverter().convertFrom(file);
if (log.isDebugEnabled()) {
log.debug("Converted pact file [" + file + "] to [" + contracts.size() + "] contracts");
}
MappingGenerator.toMappings(file, contracts, mappingsFolder);
}
private Path storeFile(Path path, byte[] contents) {
try {
Path storedPath = Files.write(path, contents);
if (log.isDebugEnabled()) {
log.debug("Stored file [" + path + "]");
}
return storedPath;
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
private String providerName(StubConfiguration stubConfiguration) {
boolean providerNameWithGroupId = Boolean.parseBoolean(StubRunnerPropertyUtils
.getProperty(this.stubRunnerOptions.getProperties(), PROVIDER_NAME_WITH_GROUP_ID));
if (providerNameWithGroupId) {
return stubConfiguration.getGroupId() + ":" + stubConfiguration.getArtifactId();
}
return stubConfiguration.getArtifactId();
}
PactLoader pactBrokerLoader(ValueResolver resolver, List<String> tags) throws IOException {
Resource repo = this.stubRunnerOptions.getStubRepositoryRoot();
String schemeSpecificPart = schemeSpecificPart(repo.getURI());
URI pactBrokerUrl = URI.create(schemeSpecificPart);
String stubRunnerUsername = this.stubRunnerOptions.getUsername() != null ? this.stubRunnerOptions.getUsername()
: "";
String stubRunnerPassword = this.stubRunnerOptions.getPassword() != null ? this.stubRunnerOptions.getPassword()
: "";
return new PactBrokerLoader(new PactBroker() {
@Override
public Class<? extends Annotation> annotationType() {
return PactBroker.class;
}
@Override
public String url() {
return resolver.resolveValue("pactbroker.url",
pactBrokerUrl.getScheme() + "://" + pactBrokerUrl.getHost() + ":" + pactBrokerUrl.getPort());
}
@Override
@Deprecated
public String host() {
return resolver.resolveValue("pactbroker.host", pactBrokerUrl.getHost());
}
@Override
@Deprecated
public String port() {
return resolver.resolveValue("pactbroker.port", String.valueOf(pactBrokerUrl.getPort()));
}
@Override
@Deprecated
public String scheme() {
return resolver.resolveValue("pactbroker.protocol", pactBrokerUrl.getScheme());
}
@Override
@Deprecated
public String[] tags() {
return new String[0];
}
@Override
public VersionSelector[] consumerVersionSelectors() {
return new VersionSelector[] { new VersionSelector() {
@Override
public Class<? extends Annotation> annotationType() {
return VersionSelector.class;
}
@Override
public String tag() {
return resolver.resolveValue("pactbroker.consumerversionselectors.tags", "");
}
@Override
public String latest() {
return resolver.resolveValue("pactbroker.consumerversionselectors.latest", "true");
}
@Override
public String consumer() {
return resolver.resolveValue("pactbroker.consumers", "");
}
@Override
public String fallbackTag() {
return resolver.resolveValue("pactbroker.fallbacktag", "");
}
} };
}
@Override
public String[] consumers() {
return new String[] { resolver.resolveValue("pactbroker.consumers", "") };
}
@Override
public PactBrokerAuth authentication() {
return new PactBrokerAuth() {
@Override
public Class<? extends Annotation> annotationType() {
return PactBrokerAuth.class;
}
@Override
public String username() {
return resolver.resolveValue("pactbroker.auth.username", stubRunnerUsername);
}
@Override
public String password() {
return resolver.resolveValue("pactbroker.auth.password", stubRunnerPassword);
}
@Override
public String token() {
return resolver.resolveValue("pactbroker.auth.token", "");
}
};
}
@Override
public Class<? extends ValueResolver> valueResolver() {
return SystemPropertyResolver.class;
}
@Override
public String enablePendingPacts() {
return resolver.resolveValue("pactbroker.enablePending", "false");
}
@Override
public String[] providerTags() {
return resolver.resolveValue("pactbroker.providerTags", "").split(",");
}
@Override
public String providerBranch() {
return resolver.resolveValue("pactbroker.providerBranch", "");
}
@Override
public String includeWipPactsSince() {
return resolver.resolveValue("pactbroker.includeWipPactsSince", "");
}
@Override
public String enableInsecureTls() {
return resolver.resolveValue("pactbroker.enableInsecureTls", "false");
}
});
}
private String schemeSpecificPart(URI uri) {
String part = uri.getSchemeSpecificPart();
if (!StringUtils.hasText(part)) {
return part;
}
return part.startsWith("//") ? part.substring(2) : part;
}
private List<String> tags(String version, ValueResolver resolver) {
String defaultTag = StubConfiguration.DEFAULT_VERSION.equals(version) ? "latest" : version;
return new ArrayList<>(Arrays.asList(
StringUtils.commaDelimitedListToStringArray(resolver.resolveValue("pactbroker.tags", defaultTag))));
}
private String toJson(Map map) {
try {
return this.objectMapper.writeValueAsString(map);
}
catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
}
private void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(
new Thread(() -> TemporaryFileStorage.cleanup(PactStubDownloader.this.deleteStubsAfterTest)));
}
}
class FromPropsThenFromSysEnv implements ValueResolver {
final SystemPropertyResolver resolver = SystemPropertyResolver.INSTANCE;
StubRunnerOptions options;
FromPropsThenFromSysEnv(StubRunnerOptions options) {
this.options = options;
}
@Override
public String resolveValue(String expression) {
return doResolve(expression, null);
}
@Nullable
private String doResolve(String expression, @Nullable String defaultValue) {
PropertyValueTuple tuple = new PropertyValueTuple(expression).invoke();
String propertyName = tuple.getPropertyName();
String property = StubRunnerPropertyUtils.getProperty(this.options.getProperties(), propertyName);
if (StringUtils.hasText(property)) {
return property;
}
return this.resolver.resolveValue(expression, defaultValue);
}
@Override
public boolean propertyDefined(String property) {
PropertyValueTuple tuple = new PropertyValueTuple(property).invoke();
String propertyName = tuple.getPropertyName();
boolean hasProperty = StubRunnerPropertyUtils.hasProperty(this.options.getProperties(), propertyName);
if (hasProperty) {
return true;
}
return this.resolver.propertyDefined(property);
}
@Nullable
@Override
public String resolveValue(String expression, String defaultValue) {
return doResolve(expression, defaultValue);
}
}
// taken from pact -
// au.com.dius.pact.provider.junit.sysprops.SystemPropertyResolver.PropertyValueTuple
class PropertyValueTuple {
private String propertyName;
PropertyValueTuple(String property) {
this.propertyName = property;
}
String getPropertyName() {
return this.propertyName;
}
PropertyValueTuple invoke() {
if (this.propertyName.contains(":")) {
String[] kv = org.apache.commons.lang3.StringUtils.splitPreserveAllTokens(this.propertyName, ':');
this.propertyName = kv[0];
}
return this;
}
}

View File

@@ -1,234 +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.spec.pact;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.consumer.dsl.PactDslJsonArray;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.core.model.OptionalBody;
import au.com.dius.pact.core.model.Request;
import au.com.dius.pact.core.model.Response;
import au.com.dius.pact.core.model.generators.Generator;
import au.com.dius.pact.core.model.messaging.Message;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.path.PathCompiler;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import groovy.lang.GString;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.contract.spec.internal.ClientDslProperty;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.ServerDslProperty;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
/**
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
final class BodyConverter {
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.INSTANCE.getMapper();
private BodyConverter() {
}
static DslPart toPactBody(DslProperty<?> dslProperty, Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
return traverse(dslProperty, null, dslPropertyValueExtractor);
}
private static DslPart traverse(Object value, DslPart parent,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
boolean isRoot = parent == null;
Object v = value;
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
}
if (v instanceof String) {
String stringValue = ((String) v).trim();
if (StringUtils.startsWith(stringValue, "{") && StringUtils.endsWith(stringValue, "}")) {
try {
v = OBJECT_MAPPER.readValue(stringValue, Object.class);
}
catch (JsonProcessingException ex) { /*
* it wasn't a JSON string after
* all...
*/
}
}
}
DslPart p = isRoot ? createRootDslPart(v) : parent;
if (v instanceof Map) {
processMap((Map) v, (PactDslJsonBody) p, dslPropertyValueExtractor);
}
else if (v instanceof Collection) {
processCollection((Collection) v, (PactDslJsonArray) p, dslPropertyValueExtractor);
}
return p;
}
private static DslPart createRootDslPart(Object value) {
return value instanceof Collection ? new PactDslJsonArray() : new PactDslJsonBody();
}
private static void processCollection(Collection values, PactDslJsonArray jsonArray,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
values.forEach(v -> {
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
}
if (v == null) {
jsonArray.nullValue();
}
else if (v instanceof String) {
jsonArray.string((String) v);
}
else if (v instanceof Number) {
jsonArray.number((Number) v);
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonArray.object();
traverse(v, current, dslPropertyValueExtractor);
current.closeObject();
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonArray.array();
traverse(v, current, dslPropertyValueExtractor);
current.closeArray();
}
});
}
private static void processMap(Map<String, Object> values, PactDslJsonBody jsonObject,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
values.forEach((k, v) -> {
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
}
if (v == null) {
jsonObject.nullValue(k);
}
else if (v instanceof Boolean) {
jsonObject.booleanValue(k, (Boolean) v);
}
else if (v instanceof String) {
jsonObject.stringType(k, (String) v);
}
else if (v instanceof Number) {
jsonObject.numberValue(k, (Number) v);
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonObject.object(k);
traverse(v, current, dslPropertyValueExtractor);
current.closeObject();
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonObject.array(k);
traverse(v, current, dslPropertyValueExtractor);
current.closeArray();
}
});
}
static Object toSCCBody(Request request) {
Object body = parseBody(request.getBody());
if (request.getGenerators().isNotEmpty() && request.getGenerators().getCategories()
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body,
request.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
currentValue -> pattern -> generatedValue -> new DslProperty<>(
new ClientDslProperty(pattern, generatedValue), currentValue));
}
return body;
}
static Object toSCCBody(Response response) {
Object body = parseBody(response.getBody());
if (response.getGenerators().isNotEmpty() && response.getGenerators().getCategories()
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body,
response.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
currentValue -> pattern -> generatedValue -> new DslProperty<>(currentValue,
new ServerDslProperty(pattern, generatedValue)));
}
return body;
}
static Object toSCCBody(Message message) {
Object body = parseBody(message.getContents());
if (message.getGenerators().isNotEmpty() && message.getGenerators().getCategories()
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
applyGenerators(body,
message.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
currentValue -> pattern -> generatedValue -> new DslProperty<>(
new ClientDslProperty(pattern, generatedValue), currentValue));
}
return body;
}
private static Object parseBody(OptionalBody optionalBody) {
if (optionalBody.isPresent()) {
try {
return OBJECT_MAPPER.readValue(optionalBody.getValue(), Object.class);
}
catch (IOException e) {
throw new RuntimeException("Body could not be read", e);
}
}
else {
return optionalBody.getValue();
}
}
private static void applyGenerators(Object body, Map<String, Generator> generatorsPerPath,
Function<Pattern, Function<Object, Function<Object, DslProperty<Object>>>> dslPropertyProvider) {
Configuration configuration = Configuration.builder().jsonProvider(new JacksonJsonProvider(OBJECT_MAPPER))
.build();
generatorsPerPath.forEach((path, generator) -> {
Path compiledPath = PathCompiler.compile(path);
EvaluationContext evaluationContext = compiledPath.evaluate(body, body, configuration, true);
evaluationContext.updateOperations().forEach(pathRef -> {
pathRef.convert(((currentValue, config) -> ValueGeneratorConverter.convert(generator,
(pattern, generatedValue) -> dslPropertyProvider.apply(pattern).apply(generatedValue)
.apply(currentValue))),
configuration);
});
});
}
}

View File

@@ -1,111 +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.spec.pact;
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher;
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory;
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.MatchingType;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
/**
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
final class MatchingRulesConverter {
private MatchingRulesConverter() {
}
static MatchingRuleCategory matchingRulesForBody(BodyMatchers bodyMatchers) {
return matchingRulesFor("body", bodyMatchers);
}
private static MatchingRuleCategory matchingRulesFor(String categoryName, BodyMatchers bodyMatchers) {
MatchingRuleCategory category = new MatchingRuleCategory(categoryName);
bodyMatchers.matchers().forEach((b) -> {
String key = getMatcherKey(b.path());
MatchingType matchingType = b.matchingType();
switch (matchingType) {
case NULL:
category.addRule(key, NullMatcher.INSTANCE);
break;
case EQUALITY:
category.addRule(key, EqualsMatcher.INSTANCE);
break;
case TYPE:
if (b.minTypeOccurrence() != null && b.maxTypeOccurrence() != null) {
category.addRule(key, new MinMaxTypeMatcher(b.minTypeOccurrence(), b.maxTypeOccurrence()));
}
else if (b.minTypeOccurrence() != null) {
category.addRule(key, new MinTypeMatcher(b.minTypeOccurrence()));
}
else if (b.maxTypeOccurrence() != null) {
category.addRule(key, new MaxTypeMatcher(b.maxTypeOccurrence()));
}
else {
category.addRule(key, TypeMatcher.INSTANCE);
}
break;
case DATE:
category.addRule(key, new DateMatcher());
break;
case TIME:
category.addRule(key, new TimeMatcher());
break;
case TIMESTAMP:
category.addRule(key, new TimestampMatcher());
break;
case REGEX:
String pattern = b.value().toString();
if (pattern.equals(RegexPatterns.number().pattern())) {
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER));
}
else if (pattern.equals(RegexPatterns.anInteger().pattern())) {
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER));
}
else if (pattern.equals(RegexPatterns.aDouble().pattern())) {
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL));
}
else {
category.addRule(key, new RegexMatcher(pattern));
}
break;
default:
break;
}
});
return category;
}
private static String getMatcherKey(String path) {
return path.startsWith("$") ? path.substring(1) : path;
}
}

View File

@@ -1,118 +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.spec.pact;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import au.com.dius.pact.consumer.MessagePactBuilder;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.core.model.messaging.MessagePact;
import groovy.lang.GString;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.Input;
import org.springframework.cloud.contract.spec.internal.OutputMessage;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
/**
* Creator of {@link MessagePact} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class MessagePactCreator {
private static final Function<DslProperty<?>, Object> clientValueExtractor = DslProperty::getClientValue;
MessagePact createFromContract(List<Contract> contracts) {
if (CollectionUtils.isEmpty(contracts)) {
return null;
}
Names names = NamingUtil.name(contracts.get(0));
MessagePactBuilder pactBuilder = new MessagePactBuilder().consumer(names.getConsumer())
.hasPactWith(names.getProducer());
for (Contract contract : contracts) {
pactBuilder = pactBuilder.given(getGiven(contract.getInput())).expectsToReceive(getOutcome(contract));
if (contract.getOutputMessage() != null) {
OutputMessage message = contract.getOutputMessage();
if (message.getBody() != null) {
DslPart pactResponseBody = BodyConverter.toPactBody(message.getBody(), clientValueExtractor);
if (message.getBodyMatchers() != null) {
pactResponseBody
.setMatchers(MatchingRulesConverter.matchingRulesForBody(message.getBodyMatchers()));
}
pactResponseBody
.setGenerators(ValueGeneratorConverter.extract(message, DslProperty::getServerValue));
pactBuilder = pactBuilder.withContent(pactResponseBody);
}
if (message.getHeaders() != null) {
pactBuilder = pactBuilder.withMetadata(getMetadata(message.getHeaders()));
}
}
}
return pactBuilder.toPact();
}
private String getGiven(Input input) {
if (input.getTriggeredBy() != null) {
return input.getTriggeredBy().getExecutionCommand();
}
else {
return "";
}
}
private String getOutcome(Contract contract) {
if (contract.getOutputMessage() != null) {
OutputMessage message = contract.getOutputMessage();
return "message sent to " + clientValueExtractor.apply(message.getSentTo());
}
else {
return "assert that " + contract.getInput().getAssertThat().getExecutionCommand();
}
}
private Map<String, String> getMetadata(Headers headers) {
return headers.getEntries().stream().collect(Collectors.toMap(Header::getName, this::extractValue));
}
private String extractValue(Object value) {
Object v = value;
if (v instanceof DslProperty) {
v = clientValueExtractor.apply((DslProperty) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, clientValueExtractor);
}
if (v instanceof String) {
return (String) v;
}
else {
return v.toString();
}
}
}

View File

@@ -1,186 +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.spec.pact;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
import au.com.dius.pact.core.model.matchingrules.MatchingRule;
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory;
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
import au.com.dius.pact.core.model.matchingrules.RuleLogic;
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
import au.com.dius.pact.core.model.messaging.Message;
import au.com.dius.pact.core.model.messaging.MessagePact;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
import org.springframework.cloud.contract.spec.internal.ResponseBodyMatchers;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
/**
* Creator of {@link Contract} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class MessagingSCContractCreator {
private static final String FULL_BODY = "$";
private static final String DESTINATION_KEY = "sentTo";
private static final List<String> NON_HEADER_META_DATA = Collections.singletonList(DESTINATION_KEY);
Collection<Contract> convertFrom(MessagePact pact) {
return pact.getMessages().stream().map(message -> Contract.make(contract -> {
contract.label(message.getDescription());
if (!message.getProviderStates().isEmpty()) {
contract.input(i -> i.triggeredBy(this.getTriggeredBy(message)));
}
contract.outputMessage((outputMessage) -> {
if (message.getContents().isPresent()) {
outputMessage.body(BodyConverter.toSCCBody(message));
MatchingRuleCategory bodyRules = message.getMatchingRules().rulesForCategory("body");
if (bodyRules != null && !bodyRules.getMatchingRules().isEmpty()) {
outputMessage.bodyMatchers((responseBodyMatchers) -> outputMessageBodyMatchers(message,
bodyRules, responseBodyMatchers));
}
}
if (!message.getMetadata().isEmpty()) {
outputMessage.headers((headers) -> outputMessageHeaders(message, headers));
}
String dest = findDestination(message);
if (StringUtils.isNotBlank(dest)) {
outputMessage.sentTo(dest);
}
});
})).collect(Collectors.toList());
}
private void outputMessageHeaders(Message message, Headers headers) {
message.getMetadata().forEach((key, value) -> {
String matchingRuleGroup = value.toString();
if (key.equalsIgnoreCase("contentType")) {
headers.messagingContentType(matchingRuleGroup);
}
else if (!NON_HEADER_META_DATA.contains(key)) {
headers.header(key, matchingRuleGroup);
}
});
}
private void outputMessageBodyMatchers(Message message, MatchingRuleCategory bodyRules,
ResponseBodyMatchers responseBodyMatchers) {
bodyRules.getMatchingRules().forEach((matchingRuleKey, matchingRuleGroup) -> {
if (matchingRuleGroup.getRuleLogic() != RuleLogic.AND) {
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported");
}
if (FULL_BODY.equals(matchingRuleKey)) {
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(message.getContents().getValue());
jsonPaths.forEach((j) -> {
responseBodyMatchers.jsonPath(j.keyBeforeChecking(), responseBodyMatchers.byType());
});
}
else {
matchingRuleGroup.getRules().forEach((rule) -> {
applyJsonPathToResponseBodyMatchers(rule, matchingRuleKey, responseBodyMatchers);
});
}
});
}
private String getTriggeredBy(Message message) {
String triggeredBy = message.getProviderStates().get(0).getName().replaceAll(":", " ").replaceAll(" ", "_")
.replaceAll("\\(", "").replaceAll("\\)", "");
return StringUtils.uncapitalize(triggeredBy) + "()";
}
private String findDestination(Message message) {
return message.getMetadata().get(DESTINATION_KEY) != null
? message.getMetadata().get(DESTINATION_KEY).toString() : "";
}
void applyJsonPathToResponseBodyMatchers(MatchingRule matchingRule, String key,
ResponseBodyMatchers responseBodyMatchers) {
if (matchingRule instanceof NullMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byNull());
}
else if (matchingRule instanceof RegexMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
}
else if (matchingRule instanceof DateMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byDate());
}
else if (matchingRule instanceof TimeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byTime());
}
else if (matchingRule instanceof TimestampMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byTimestamp());
}
else if (matchingRule instanceof MinTypeMatcher) {
responseBodyMatchers.jsonPath(key,
responseBodyMatchers.byType((b) -> b.minOccurrence(((MinTypeMatcher) matchingRule).getMin())));
}
else if (matchingRule instanceof MinMaxTypeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType((c) -> {
c.minOccurrence(((MinMaxTypeMatcher) matchingRule).getMin());
c.maxOccurrence(((MinMaxTypeMatcher) matchingRule).getMax());
}));
}
else if (matchingRule instanceof MaxTypeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType((c) -> {
c.maxOccurrence(((MaxTypeMatcher) matchingRule).getMax());
}));
}
else if (matchingRule instanceof TypeMatcher) {
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType());
}
else if (matchingRule instanceof NumberTypeMatcher) {
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
case NUMBER:
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.number()));
break;
case INTEGER:
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.anInteger()));
break;
case DECIMAL:
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.aDouble()));
break;
default:
throw new RuntimeException("Unsupported number type!");
}
}
}
}

View File

@@ -1,51 +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.spec.pact;
/**
* @author Marcin Grzejszczak
* @author Stessy Delcroix
* @since
*/
class Names {
private final String consumer;
private final String producer;
private final String test;
Names(String[] strings) {
this.consumer = strings[0];
this.producer = strings[1];
this.test = strings.length >= 2 ? strings[2] : "";
}
@Override
public String toString() {
return this.consumer + "_" + this.producer;
}
String getConsumer() {
return consumer;
}
String getProducer() {
return producer;
}
}

View File

@@ -1,42 +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.spec.pact;
import org.springframework.cloud.contract.spec.Contract;
/**
* @author Marcin Grzejszczak
* @author Stessy Delcroix
* @since
*/
final class NamingUtil {
// consumer___producer___testname
private static final String SEPARATOR = "___";
private NamingUtil() {
}
static Names name(Contract contract) {
String contractName = contract.getName();
if (contractName == null || !contractName.contains(SEPARATOR)) {
return new Names(new String[] { "Consumer", "Provider", "" });
}
return new Names(contractName.split(SEPARATOR));
}
}

View File

@@ -1,49 +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.spec.pact;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link ObjectMapper} singleton instance creation.
* @author Stessy Delcroix
* @since 2.0.0
*/
public enum ObjectMapperFactory {
/**
* Singleton instance constant.
*/
INSTANCE;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
static {
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
OBJECT_MAPPER.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
}
public ObjectMapper getMapper() {
return OBJECT_MAPPER;
}
}

View File

@@ -1,152 +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.spec.pact;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import au.com.dius.pact.core.model.DefaultPactReader;
import au.com.dius.pact.core.model.Pact;
import au.com.dius.pact.core.model.PactSpecVersion;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.messaging.MessagePact;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.core.util.Separators;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.ContractConverter;
/**
* Converter of JSON PACT file.
*
* @author Marcin Grzejszczak
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 1.1.0
*/
public class PactContractConverter implements ContractConverter<Collection<Pact>> {
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.INSTANCE.getMapper();
private final RequestResponseSCContractCreator requestResponseSCContractCreator = new RequestResponseSCContractCreator();
private final MessagingSCContractCreator messagingSCContractCreator = new MessagingSCContractCreator();
private final RequestResponsePactCreator requestResponsePactCreator = new RequestResponsePactCreator();
private final MessagePactCreator messagePactCreator = new MessagePactCreator();
@Override
public boolean isAccepted(File file) {
try {
Pact pact = DefaultPactReader.INSTANCE.loadPact(file);
return !pact.getInteractions().isEmpty();
}
catch (Exception e) {
return false;
}
}
@Override
public Collection<Contract> convertFrom(File file) {
Pact pact = DefaultPactReader.INSTANCE.loadPact(file);
if (pact instanceof RequestResponsePact) {
return requestResponseSCContractCreator.convertFrom((RequestResponsePact) pact);
}
if (pact instanceof MessagePact) {
return messagingSCContractCreator.convertFrom((MessagePact) pact);
}
throw new UnsupportedOperationException(
"We currently don't support pact contracts of type" + pact.getClass().getSimpleName());
}
@Override
public Collection<Pact> convertTo(Collection<Contract> contracts) {
List<Pact> pactContracts = new ArrayList<>();
Map<String, List<Contract>> groupedContracts = contracts.stream()
.collect(Collectors.groupingBy(c -> NamingUtil.name(c).toString()));
for (List<Contract> list : groupedContracts.values()) {
List<Contract> httpOnly = list.stream().filter(c -> c.getRequest() != null).collect(Collectors.toList());
List<Contract> messagingOnly = list.stream().filter(c -> c.getInput() != null).collect(Collectors.toList());
RequestResponsePact responsePact = requestResponsePactCreator.createFromContract(httpOnly);
if (responsePact != null) {
pactContracts.add(responsePact);
}
MessagePact messagePact = messagePactCreator.createFromContract(messagingOnly);
if (messagePact != null) {
pactContracts.add(messagePact);
}
}
return pactContracts;
}
@Override
public Map<String, byte[]> store(Collection<Pact> contracts) {
return contracts.stream().collect(Collectors.toMap(this::name, c -> {
try {
return this.buildPrettyPrint(OBJECT_MAPPER.writeValueAsString(c.toMap(PactSpecVersion.V3))).getBytes();
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException("The pact contract is not a valid map", e);
}
}));
}
protected String name(Pact contract) {
return contract.getConsumer().getName() + "_" + contract.getProvider().getName() + "_"
+ Math.abs(contract.hashCode()) + ".json";
}
private String buildPrettyPrint(String contract) {
try {
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.reader().readValue(contract, Object.class);
DefaultIndenter customIndenter = new DefaultIndenter(" ", "\n");
return OBJECT_MAPPER
.writer(new CustomPrettyPrinter().withArrayIndenter(customIndenter)
.withObjectIndenter(customIndenter))
.writeValueAsString(intermediateObjectForPrettyPrinting);
}
catch (IOException e) {
throw new RuntimeException("WireMock response body could not be pretty printed");
}
}
private static class CustomPrettyPrinter extends DefaultPrettyPrinter {
@Override
public CustomPrettyPrinter createInstance() {
return new CustomPrettyPrinter();
}
@Override
public DefaultPrettyPrinter withSeparators(Separators separators) {
_separators = separators;
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
return this;
}
}
}

View File

@@ -1,94 +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.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<ProviderStateMetadata> providerStates = new ArrayList<>();
public List<ProviderStateMetadata> getProviderStates() {
return this.providerStates;
}
public void setProviderStates(List<ProviderStateMetadata> providerStates) {
this.providerStates = providerStates;
}
@NonNull
public static PactMetaData fromMetadata(Map<String, Object> 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<String, Object> params;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, Object> getParams() {
return this.params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
}
}

View File

@@ -1,356 +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.spec.pact;
import java.util.Collection;
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;
import au.com.dius.pact.consumer.ConsumerPactBuilder;
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;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.Body;
import org.springframework.cloud.contract.spec.internal.Cookies;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.ExecutionProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.QueryParameters;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Response;
import org.springframework.util.CollectionUtils;
import static java.util.stream.Collectors.joining;
/**
* Creator of {@link RequestResponsePact} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class RequestResponsePactCreator {
RequestResponsePact createFromContract(List<Contract> contracts) {
if (CollectionUtils.isEmpty(contracts)) {
return null;
}
Names names = NamingUtil.name(contracts.get(0));
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder.consumer(names.getConsumer())
.hasPactWith(names.getProducer());
PactDslResponse pactDslResponse = null;
for (Contract contract : contracts) {
assertNoExecutionProperty(contract);
PactDslRequestWithPath pactDslRequest = pactDslResponse != null
? createPactDslRequestWithPath(contract, pactDslResponse)
: createPactDslRequestWithPath(contract, pactDslWithProvider);
pactDslResponse = createPactDslResponse(contract, pactDslRequest);
}
return pactDslResponse.toPact();
}
private void assertNoExecutionProperty(Contract contract) {
assertNoExecutionPropertyInBody(contract.getRequest().getBody(), DslProperty::getServerValue);
assertNoExecutionPropertyInBody(contract.getResponse().getBody(), DslProperty::getClientValue);
}
private void assertNoExecutionPropertyInBody(Body body,
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
traverseValues(body, dslPropertyValueExtractor, (Object object) -> {
if (object instanceof ExecutionProperty) {
throw new UnsupportedOperationException("We can't convert a contract that has execution property");
}
return object;
});
}
private void traverseValues(Object value, Function<DslProperty<?>, Object> dslPropertyValueExtractor,
Function<Object, Object> function) {
if (value instanceof DslProperty) {
traverseValues(dslPropertyValueExtractor.apply((DslProperty<?>) value), dslPropertyValueExtractor,
function);
}
else if (value instanceof Map) {
((Map) value).values().forEach(v -> traverseValues(v, dslPropertyValueExtractor, function));
}
else if (value instanceof Collection) {
((Collection<?>) value).forEach(v -> traverseValues(v, dslPropertyValueExtractor, function));
}
else {
function.apply(value);
}
}
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslResponse pactDslResponse) {
PactDslRequestWithPath pactDslRequest = getPactDslRequest(contract,
getPactDslWithStateFunction(pactDslResponse), getPactDslRequestWithPathBiFunction(pactDslResponse));
Request request = contract.getRequest();
final PactDslRequestWithPath finalPactDslRequest = pactDslRequest;
if (request.getHeaders() != null) {
request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h));
}
if (request.getCookies() != null) {
pactDslRequest = processCookies(finalPactDslRequest, request.getCookies());
}
if (request.getBody() != null) {
DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getClientValue);
if (request.getBodyMatchers() != null) {
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.getBodyMatchers()));
}
pactRequestBody
.setGenerators(ValueGeneratorConverter.extract(request.getBody(), DslProperty::getClientValue));
pactDslRequest = pactDslRequest.body(pactRequestBody);
}
return pactDslRequest;
}
private Function<PactMetaData.ProviderStateMetadata, PactDslWithState> getPactDslWithStateFunction(
PactDslResponse pactDslResponse) {
return stateMetadata -> pactDslResponse.given(stateMetadata.getName(), stateMetadata.getParams());
}
private BiFunction<String, Request, PactDslRequestWithPath> 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();
final PactDslRequestWithPath finalPactDslRequest = pactDslRequest;
if (request.getHeaders() != null) {
request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h));
}
if (request.getBody() != null) {
DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getServerValue);
if (request.getBodyMatchers() != null) {
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.getBodyMatchers()));
}
pactRequestBody
.setGenerators(ValueGeneratorConverter.extract(request.getBody(), DslProperty::getClientValue));
pactDslRequest = pactDslRequest.body(pactRequestBody);
}
return pactDslRequest;
}
private Function<PactMetaData.ProviderStateMetadata, PactDslWithState> getPactDslWithStateFunction(
PactDslWithProvider pactDslWithProvider) {
return stateMetadata -> pactDslWithProvider.given(stateMetadata.getName(), stateMetadata.getParams());
}
private BiFunction<String, Request, PactDslRequestWithPath> getPactDslRequestWithPathBiFunction(
PactDslWithProvider pactDslWithProvider) {
return (description, request) -> pactDslWithProvider.uponReceiving(description).path(url(request))
.method(request.getMethod().getServerValue().toString());
}
private PactDslRequestWithPath getPactDslRequest(Contract contract,
Function<PactMetaData.ProviderStateMetadata, PactDslWithState> pactDslWithStateFunction,
BiFunction<String, Request, PactDslRequestWithPath> 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<PactMetaData.ProviderStateMetadata, PactDslWithState> 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();
}
else if (request.getUrl() != null) {
return request.getUrl().getServerValue().toString();
}
throw new IllegalStateException("No url provided");
}
private String query(Request request) {
final StringBuilder query = new StringBuilder();
QueryParameters params = queryParams(request);
if (params != null) {
AtomicInteger index = new AtomicInteger();
params.getParameters().forEach(p -> {
query.append(p.getName()).append('=').append(p.getServerValue());
if (index.incrementAndGet() < params.getParameters().size()) {
query.append('&');
}
});
}
return query.toString();
}
private QueryParameters queryParams(Request request) {
if (request.getUrlPath() != null) {
return request.getUrlPath().getQueryParameters();
}
else if (request.getUrl() != null) {
return request.getUrl().getQueryParameters();
}
throw new IllegalStateException("No url provided");
}
private PactDslRequestWithPath processHeader(PactDslRequestWithPath pactDslRequest, Header header) {
if (header.isSingleValue()) {
String value = getDslPropertyServerValue(header).toString();
return pactDslRequest.headers(header.getName(), value);
}
else {
String regex = getDslPropertyClientValue(header).toString();
String example = getDslPropertyServerValue(header).toString();
return pactDslRequest.matchHeader(header.getName(), regex, example);
}
}
private String stubSideCookieExample(Cookies cookies) {
return cookies.asStubSideMap().entrySet().stream().map(Object::toString).collect(joining(";"));
}
private Object getDslPropertyClientValue(Object o) {
Object value = o;
if (value instanceof DslProperty) {
value = getDslPropertyClientValue(((DslProperty) value).getClientValue());
}
return value;
}
private Object getDslPropertyServerValue(Object o) {
Object value = o;
if (value instanceof DslProperty) {
value = getDslPropertyServerValue(((DslProperty) value).getServerValue());
}
return value;
}
private PactDslRequestWithPath processCookies(PactDslRequestWithPath pactDslRequest, Cookies cookies) {
Map<String, Object> stubSideCookies = cookies.asStubSideMap();
Collection<RegexProperty> regexProperties = stubSideCookies.values().stream()
.filter(r -> r instanceof Pattern || r instanceof RegexProperty).map(RegexProperty::new)
.collect(Collectors.toList());
if (!regexProperties.isEmpty()) {
String regex = regexProperties.stream().map(RegexProperty::pattern).collect(joining("|"));
return pactDslRequest.matchHeader("Cookie", regex, testSideCookieExample(cookies));
}
else {
return pactDslRequest.headers("Cookie", testSideCookieExample(cookies));
}
}
private String testSideCookieExample(Cookies cookies) {
return cookies.asTestSideMap().entrySet().stream().map(Object::toString).collect(joining(";"));
}
private PactDslResponse createPactDslResponse(Contract contract, PactDslRequestWithPath pactDslRequest) {
Response response = contract.getResponse();
PactDslResponse pactDslResponse = pactDslRequest.willRespondWith()
.status((Integer) response.getStatus().getClientValue());
PactDslResponse finalPactDslResponse = pactDslResponse;
if (response.getHeaders() != null) {
response.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslResponse, h));
}
if (response.getCookies() != null) {
pactDslResponse = processCookies(pactDslResponse, response.getCookies());
}
if (response.getBody() != null) {
DslPart pactResponseBody = BodyConverter.toPactBody(response.getBody(), DslProperty::getClientValue);
if (response.getBodyMatchers() != null) {
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(response.getBodyMatchers()));
}
pactResponseBody
.setGenerators(ValueGeneratorConverter.extract(response.getBody(), DslProperty::getServerValue));
pactDslResponse = pactDslResponse.body(pactResponseBody);
}
return pactDslResponse;
}
private PactDslResponse processHeader(PactDslResponse pactDslResponse, Header header) {
if (header.isSingleValue()) {
String value = getDslPropertyClientValue(header).toString();
return pactDslResponse.headers(Collections.singletonMap(header.getName(), value));
}
else {
String regex = getDslPropertyServerValue(header).toString();
String example = getDslPropertyClientValue(header).toString();
return pactDslResponse.matchHeader(header.getName(), regex, example);
}
}
private PactDslResponse processCookies(PactDslResponse pactDslResponse, Cookies cookies) {
Map<String, Object> testSideCookies = cookies.asTestSideMap();
Collection<RegexProperty> regexProperties = testSideCookies.values().stream()
.filter(p -> p instanceof Pattern || p instanceof RegexProperty).map(RegexProperty::new)
.collect(Collectors.toList());
if (!regexProperties.isEmpty()) {
String regex = regexProperties.stream().map(RegexProperty::pattern).collect(joining("|"));
return pactDslResponse.matchHeader("Cookie", regex, stubSideCookieExample(cookies));
}
else {
return pactDslResponse.headers(Collections.singletonMap("Cookie", stubSideCookieExample(cookies)));
}
}
}

View File

@@ -1,432 +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.spec.pact;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import au.com.dius.pact.core.model.Interaction;
import au.com.dius.pact.core.model.OptionalBody;
import au.com.dius.pact.core.model.ProviderState;
import au.com.dius.pact.core.model.Request;
import au.com.dius.pact.core.model.RequestResponseInteraction;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.Response;
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
import au.com.dius.pact.core.model.matchingrules.MatchingRule;
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory;
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup;
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
import au.com.dius.pact.core.model.matchingrules.RuleLogic;
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
/**
* Creator of {@link Contract} instances.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
class RequestResponseSCContractCreator {
private static final String FULL_BODY = "$";
Collection<Contract> convertFrom(RequestResponsePact pact) {
return pact.getInteractions().stream().map(interaction -> Contract.make(contract -> {
mapContractDescription(interaction, contract);
mapContractRequest(interaction, contract);
mapContractResponse(interaction, contract);
})).collect(Collectors.toList());
}
private void mapContractDescription(Interaction interaction, Contract contract) {
contract.description(buildDescription(interaction));
}
private void mapContractRequest(Interaction inter, Contract contract) {
contract.request((contractRequest) -> {
if (!(inter instanceof RequestResponseInteraction)) {
return;
}
RequestResponseInteraction interaction = (RequestResponseInteraction) inter;
Request pactRequest = interaction.getRequest();
contractRequest.method(pactRequest.getMethod());
mapRequestUrl(contractRequest, pactRequest);
if (!pactRequest.getHeaders().isEmpty()) {
mapRequestHeaders(contractRequest, pactRequest);
}
if (pactRequest.getHeaders().containsKey("Cookie")) {
mapRequestCookies(contractRequest, pactRequest);
}
if (pactRequest.getBody().getState() == OptionalBody.State.PRESENT) {
mapRequestBody(contractRequest, pactRequest);
}
MatchingRuleCategory bodyRules = pactRequest.getMatchingRules().rulesForCategory("body");
if (!bodyRules.getMatchingRules().isEmpty()) {
mapRequestBodyRules(contractRequest, bodyRules);
}
});
}
private void mapContractResponse(Interaction inter, Contract contract) {
contract.response((contractResponse) -> {
if (!(inter instanceof RequestResponseInteraction)) {
return;
}
RequestResponseInteraction interaction = (RequestResponseInteraction) inter;
Response pactResponse = interaction.getResponse();
contractResponse.status(pactResponse.getStatus());
if (pactResponse.getBody().isPresent()) {
mapResponseBody(contractResponse, pactResponse);
}
MatchingRuleCategory bodyRules = pactResponse.getMatchingRules().rulesForCategory("body");
if (!bodyRules.getMatchingRules().isEmpty()) {
mapResponseBodyRules(contractResponse, pactResponse, bodyRules);
}
if (!pactResponse.getHeaders().isEmpty()) {
mapResponseHeaders(contractResponse, pactResponse);
}
if (pactResponse.getHeaders().containsKey("Cookie")) {
mapResponseCookies(contractResponse, pactResponse);
}
});
}
private void mapResponseBodyRules(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse, MatchingRuleCategory bodyRules) {
contractResponse.bodyMatchers((bodyMatchers) -> {
bodyRules.getMatchingRules().forEach((key, matchindRuleGroup) -> {
if (matchindRuleGroup.getRuleLogic() != RuleLogic.AND) {
throw new UnsupportedOperationException(
"Currently only the AND combination rule logic is supported");
}
if (FULL_BODY.equals(key)) {
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(
new String(pactResponse.getBody().getValue()));
jsonPaths.forEach(
(jsonPath) -> bodyMatchers.jsonPath(jsonPath.keyBeforeChecking(), bodyMatchers.byType()));
}
else {
matchindRuleGroup.getRules().forEach((matchingRule) -> {
if (matchingRule instanceof NullMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byNull());
}
else if (matchingRule instanceof RegexMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
}
else if (matchingRule instanceof DateMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byDate());
}
else if (matchingRule instanceof TimeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTime());
}
else if (matchingRule instanceof TimestampMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTimestamp());
}
else if (matchingRule instanceof MinTypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> valueHolder
.minOccurrence((((MinTypeMatcher) matchingRule).getMin()))));
}
else if (matchingRule instanceof MinMaxTypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> {
valueHolder.minOccurrence((((MinMaxTypeMatcher) matchingRule).getMin()));
valueHolder.maxOccurrence((((MinMaxTypeMatcher) matchingRule).getMax()));
}));
}
else if (matchingRule instanceof MaxTypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> valueHolder
.maxOccurrence((((MaxTypeMatcher) matchingRule).getMax()))));
}
else if (matchingRule instanceof TypeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byType());
}
else if (matchingRule instanceof NumberTypeMatcher) {
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
case NUMBER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.number()));
break;
case INTEGER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.anInteger()));
break;
case DECIMAL:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.aDouble()));
break;
default:
throw new UnsupportedOperationException("Unsupported number type!");
}
}
});
}
});
});
}
private void mapResponseBody(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse) {
Object parsedBody = BodyConverter.toSCCBody(pactResponse);
if (parsedBody instanceof Map) {
contractResponse.body((Map) parsedBody);
}
else if (parsedBody instanceof List) {
contractResponse.body((List) parsedBody);
}
else {
contractResponse.body(parsedBody.toString());
}
}
private void mapRequestBodyRules(org.springframework.cloud.contract.spec.internal.Request contractRequest,
MatchingRuleCategory bodyRules) {
contractRequest.bodyMatchers((bodyMatchers) -> {
bodyRules.getMatchingRules().forEach((key, matchingRuleGroup) -> {
if (matchingRuleGroup.getRuleLogic() != RuleLogic.AND) {
throw new UnsupportedOperationException(
"Currently only the AND combination rule logic is supported");
}
matchingRuleGroup.getRules().forEach((matchingRule) -> {
if (matchingRule instanceof RegexMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
}
else if (matchingRule instanceof DateMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byDate());
}
else if (matchingRule instanceof TimeMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTime());
}
else if (matchingRule instanceof TimestampMatcher) {
bodyMatchers.jsonPath(key, bodyMatchers.byTimestamp());
}
else if (matchingRule instanceof NumberTypeMatcher) {
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
case NUMBER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.number()));
break;
case INTEGER:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.anInteger()));
break;
case DECIMAL:
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.aDouble()));
break;
default:
throw new RuntimeException("Unsupported number type!");
}
}
});
});
});
}
private void mapRequestBody(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
Object parsedBody = BodyConverter.toSCCBody(pactRequest);
if (parsedBody instanceof Map) {
contractRequest.body((Map) parsedBody);
}
else if (parsedBody instanceof List) {
contractRequest.body((List) parsedBody);
}
else {
contractRequest.body(parsedBody.toString());
}
}
private void mapRequestCookies(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
MatchingRuleCategory headerRules = pactRequest.getMatchingRules().rulesForCategory("header");
String[] splitCookiesHeader = pactRequest.getHeaders().get("Cookie").get(0).split(";");
Map<String, String> foundCookies = Stream.of(splitCookiesHeader).map((cookieHeader) -> cookieHeader.split("="))
.collect(Collectors.toMap(splittedCookieHeader -> splittedCookieHeader[0],
splittedCookieHeader -> splittedCookieHeader[1]));
contractRequest.cookies((cookies) -> foundCookies.forEach((key, value) -> {
if (headerRules.getMatchingRules().containsKey("Cookie")) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get("Cookie");
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
cookies.cookie(key,
contractRequest.$(
contractRequest.c(contractRequest.regex(((RegexMatcher) matchingRule).getRegex())),
contractRequest.p(value)));
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
cookies.cookie(key, value);
}
}));
}
private void mapResponseCookies(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse) {
MatchingRuleCategory headerRules = pactResponse.getMatchingRules().rulesForCategory("header");
String[] splitCookiesHeader = pactResponse.getHeaders().get("Cookie").get(0).split(";");
Map<String, String> foundCookies = Stream.of(splitCookiesHeader).map((cookieHeader) -> cookieHeader.split("="))
.collect(Collectors.toMap(splittedCookieHeader -> splittedCookieHeader[0],
splittedCookieHeader -> splittedCookieHeader[1]));
contractResponse.cookies((cookies) -> foundCookies.forEach((key, value) -> {
if (headerRules.getMatchingRules().containsKey("Cookie")) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get("Cookie");
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
cookies.cookie(key, contractResponse.$(
contractResponse.p(
contractResponse.regex(Pattern.compile(((RegexMatcher) matchingRule).getRegex()))),
contractResponse.c(value)));
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
cookies.cookie(key, value);
}
}));
}
private void mapRequestUrl(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
if (!pactRequest.getQuery().isEmpty()) {
contractRequest.url(pactRequest.getPath(),
(url) -> url.queryParameters((queryParameters) -> pactRequest.getQuery().forEach((key,
values) -> values.forEach((singleValue) -> queryParameters.parameter(key, singleValue)))));
}
else {
contractRequest.url(pactRequest.getPath());
}
}
private void mapRequestHeaders(org.springframework.cloud.contract.spec.internal.Request contractRequest,
Request pactRequest) {
MatchingRuleCategory headerRules = pactRequest.getMatchingRules().rulesForCategory("header");
contractRequest.headers((headers) -> pactRequest.getHeaders().forEach((key, values) -> {
if (key.equalsIgnoreCase("Cookie")) {
return;
}
if (headerRules.getMatchingRules().containsKey(key)) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get(key);
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
values.forEach((value) -> {
headers.header(key,
contractRequest.$(
contractRequest
.c(contractRequest.regex(((RegexMatcher) matchingRule).getRegex())),
contractRequest.p(value)));
});
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
values.forEach((value) -> headers.header(key, value));
}
}));
}
private void mapResponseHeaders(org.springframework.cloud.contract.spec.internal.Response contractResponse,
Response pactResponse) {
MatchingRuleCategory headerRules = pactResponse.getMatchingRules().rulesForCategory("header");
contractResponse.headers((headers) -> pactResponse.getHeaders().forEach((key, values) -> {
if (key.equalsIgnoreCase("Cookie")) {
return;
}
if (headerRules.getMatchingRules().containsKey(key)) {
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get(key);
if (matchingRuleGroup.getRules().size() > 1) {
throw new UnsupportedOperationException(
"Currently only 1 rule at a time for a header is supported");
}
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
if (matchingRule instanceof RegexMatcher) {
values.forEach((value) -> {
headers.header(key,
contractResponse.$(
contractResponse.p(contractResponse
.regex(Pattern.compile(((RegexMatcher) matchingRule).getRegex()))),
contractResponse.c(value)));
});
}
else {
throw new UnsupportedOperationException(
"Currently only the header matcher of type regex is supported");
}
}
else {
values.forEach((value) -> headers.header(key, value));
}
}));
}
private String buildDescription(Interaction interaction) {
StringBuilder description = new StringBuilder(interaction.getDescription());
List<ProviderState> providerStates = interaction.getProviderStates();
for (ProviderState providerState : providerStates) {
description.append(" ").append(providerState.getName());
Map<String, Object> params = providerState.getParams();
if (!params.isEmpty()) {
description.append("(");
params.forEach((k, v) -> {
description.append(k).append(": ").append(v.toString()).append(", ");
});
description.delete(description.length() - 2, description.length());
description.append(")");
}
}
return description.toString();
}
}

View File

@@ -1,215 +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.spec.pact;
import java.util.Collection;
import java.util.HashMap;
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 au.com.dius.pact.core.model.generators.Category;
import au.com.dius.pact.core.model.generators.DateGenerator;
import au.com.dius.pact.core.model.generators.DateTimeGenerator;
import au.com.dius.pact.core.model.generators.Generator;
import au.com.dius.pact.core.model.generators.Generators;
import au.com.dius.pact.core.model.generators.RandomBooleanGenerator;
import au.com.dius.pact.core.model.generators.RandomDecimalGenerator;
import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator;
import au.com.dius.pact.core.model.generators.RandomIntGenerator;
import au.com.dius.pact.core.model.generators.RandomStringGenerator;
import au.com.dius.pact.core.model.generators.RegexGenerator;
import au.com.dius.pact.core.model.generators.TimeGenerator;
import au.com.dius.pact.core.model.generators.UuidGenerator;
import groovy.lang.GString;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.contract.spec.internal.Body;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.OutputMessage;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
/**
* Convert a value for a given {@link Generator}.
*
* @author Tim Ysewyn
* @author Stessy Delcroix
* @since 2.0.0
*/
final class ValueGeneratorConverter {
private static final String INTEGER_PATTERN = "-?(\\d+)";
private static final Pattern INTEGER = Pattern.compile(INTEGER_PATTERN);
private static final String DECIMAL_PATTERN = "-?(\\d*\\.\\d+)";
private static final Pattern DECIMAL = Pattern.compile(DECIMAL_PATTERN);
private static final String HEX_PATTERN = "[a-fA-F0-9]+";
private static final Pattern HEX = Pattern.compile(HEX_PATTERN);
private static final String ALPHA_NUMERIC_PATTERN = "[a-zA-Z0-9]+";
private static final Pattern ALPHA_NUMERIC = Pattern.compile(ALPHA_NUMERIC_PATTERN);
private static final String UUID_PATTERN = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}";
private static final Pattern UUID = Pattern.compile(UUID_PATTERN);
private static final String ANY_DATE_PATTERN = "(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])";
private static final Pattern ANY_DATE = Pattern.compile(ANY_DATE_PATTERN);
private static final String ANY_TIME_PATTERN = "(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])";
private static final Pattern ANY_TIME = Pattern.compile(ANY_TIME_PATTERN);
private static final String ANY_DATE_TIME_PATTERN = "([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])";
private static final Pattern ANY_DATE_TIME = Pattern.compile(ANY_DATE_TIME_PATTERN);
private static final String TRUE_OR_FALSE_PATTERN = "(true|false)";
private static final Pattern TRUE_OR_FALSE = Pattern.compile(TRUE_OR_FALSE_PATTERN);
private ValueGeneratorConverter() {
}
static DslProperty<Object> convert(Generator generator,
BiFunction<Pattern, Object, DslProperty<Object>> dslPropertyProvider) {
Pattern pattern = null;
if (generator instanceof RandomIntGenerator) {
pattern = INTEGER;
}
else if (generator instanceof RandomDecimalGenerator) {
pattern = DECIMAL;
}
else if (generator instanceof RandomHexadecimalGenerator) {
pattern = HEX;
}
else if (generator instanceof RandomStringGenerator) {
pattern = ALPHA_NUMERIC;
}
else if (generator instanceof RegexGenerator) {
pattern = Pattern.compile(((RegexGenerator) generator).getRegex());
}
else if (generator instanceof UuidGenerator) {
pattern = UUID;
}
else if (generator instanceof DateGenerator) {
pattern = getDateTimePattern(((DateGenerator) generator).getFormat(), ANY_DATE);
}
else if (generator instanceof TimeGenerator) {
pattern = getDateTimePattern(((TimeGenerator) generator).getFormat(), ANY_TIME);
}
else if (generator instanceof DateTimeGenerator) {
pattern = getDateTimePattern(((DateTimeGenerator) generator).getFormat(), ANY_DATE_TIME);
}
else if (generator instanceof RandomBooleanGenerator) {
pattern = TRUE_OR_FALSE;
}
if (pattern == null) {
throw new UnsupportedOperationException(
"We currently don't support a generator of type " + generator.getClass().getSimpleName());
}
else {
Object generatedValue = generator.generate(new HashMap<>(), null);
return dslPropertyProvider.apply(pattern, generatedValue);
}
}
private static Pattern getDateTimePattern(String format, Pattern defaultPattern) {
return StringUtils.isNotBlank(format) ? Pattern.compile(format) : defaultPattern;
}
static Generators extract(Body body, Function<DslProperty<?>, Object> dslPropertyValueProvider) {
Generators generators = new Generators();
traverse(body, dslPropertyValueProvider, "", generators, Category.BODY);
return generators;
}
static Generators extract(OutputMessage message, Function<DslProperty<?>, Object> dslPropertyValueProvider) {
Generators generators = new Generators();
traverse(message.getBody(), dslPropertyValueProvider, "", generators, Category.BODY);
return generators;
}
private static void traverse(Object value, Function<DslProperty<?>, Object> dslPropertyValueProvider, String path,
Generators generators, Category category) {
Object v = value;
if (v instanceof DslProperty) {
v = dslPropertyValueProvider.apply((DslProperty<?>) v);
}
if (v instanceof GString) {
v = ContentUtils.extractValue((GString) v, dslPropertyValueProvider);
}
if (v instanceof Map) {
((Map) v).forEach(
(key, val) -> traverse(val, dslPropertyValueProvider, path + "." + key, generators, category));
}
else if (v instanceof Collection) {
AtomicInteger index = new AtomicInteger();
((Collection<?>) v).forEach(val -> traverse(val, dslPropertyValueProvider,
path + "[" + index.getAndIncrement() + "]", generators, category));
}
else if (v instanceof DslProperty) {
traverse(v, dslPropertyValueProvider, path, generators, category);
}
else if (v instanceof RegexProperty || v instanceof Pattern) {
RegexProperty regexProperty = new RegexProperty(v);
switch (regexProperty.pattern()) {
case INTEGER_PATTERN:
generators.addGenerator(category, path, new RandomIntGenerator(0, Integer.MAX_VALUE));
break;
case DECIMAL_PATTERN:
generators.addGenerator(category, path, new RandomDecimalGenerator(10));
break;
case HEX_PATTERN:
generators.addGenerator(category, path, new RandomHexadecimalGenerator(10));
break;
case ALPHA_NUMERIC_PATTERN:
generators.addGenerator(category, path, new RandomStringGenerator(10));
break;
case UUID_PATTERN:
generators.addGenerator(category, path, new UuidGenerator());
break;
case ANY_DATE_PATTERN:
generators.addGenerator(category, path, new DateGenerator());
break;
case ANY_TIME_PATTERN:
generators.addGenerator(category, path, new TimeGenerator());
break;
case ANY_DATE_TIME_PATTERN:
generators.addGenerator(category, path, new DateTimeGenerator());
break;
case TRUE_OR_FALSE_PATTERN:
generators.addGenerator(category, path, RandomBooleanGenerator.INSTANCE);
break;
default:
generators.addGenerator(category, path, new RegexGenerator(regexProperty.pattern()));
break;
}
}
}
}

View File

@@ -1,4 +0,0 @@
org.springframework.cloud.contract.spec.ContractConverter=\
org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
org.springframework.cloud.contract.stubrunner.PactStubDownloaderBuilder

View File

@@ -1,165 +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.stubrunner
import java.nio.file.Files
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.model.PactSource
import au.com.dius.pact.provider.junitsupport.loader.PactLoader
import au.com.dius.pact.core.support.expressions.ValueResolver
import com.github.tomakehurst.wiremock.stubbing.StubMapping
import spock.lang.Ignore
import spock.lang.Specification
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
/**
* @author Marcin Grzejszczak
* @author Tim Ysewyn
*/
@Ignore("Flakey")
class PactStubDownloaderBuilderSpec extends Specification {
def "should retrieve pacts from broker"() throws IOException {
given:
Collection<Pact> pacts = new PactContractConverter().
convertTo([Contract.make {
request {
url "/foo"
method GET()
}
response {
status OK()
}
}, Contract.make {
request {
url "/bar"
method GET()
}
response {
status OK()
}
}])
StubRunnerOptions options = new StubRunnerOptionsBuilder()
.withProperties(props())
.build()
PactStubDownloader downloader = new PactStubDownloader(options) {
@Override
PactLoader pactBrokerLoader(ValueResolver resolver,
List<String> tags) {
return new PactLoader() {
@Override
List<Pact> load(String providerName) {
return pacts
}
@Override
PactSource getPactSource() {
return null
}
}
}
}
when:
Map.Entry<StubConfiguration, File> entry = downloader
.
downloadAndUnpackStubJar(new StubConfiguration("com.example:bobby:+:classifier"))
then:
entry != null
entry.getValue().exists()
File contracts = new File(entry.getValue(), "com/example/bobby/contracts")
contracts.exists()
contracts.list() != null
File mappings = new File(entry.getValue(), "com/example/bobby/mappings")
mappings.exists()
mappings.list() != null
mappings.list().size() == 2
StubMapping.buildFrom(new String(Files.
readAllBytes(mappings.listFiles()[0].toPath())))
StubMapping.buildFrom(new String(Files.
readAllBytes(mappings.listFiles()[1].toPath())))
}
Map<String, String> props() {
Map<String, String> map = new HashMap<>()
// map.put("pactbroker.host", "localhost")
// map.put("pactbroker.port", String.valueOf(this.port))
// map.put("pactbroker.host", "test.pact.dius.com.au")
// map.put("pactbroker.port", "443")
// map.put("pactbroker.protocol", "https")
// map.put("pactbroker.auth.scheme", "Basic")
// map.put("pactbroker.auth.username", "dXfltyFMgNOFZAxr8io9wJ37iUpY42M")
// map.put("pactbroker.auth.password", "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1")
return map
}
// @After
// void tearDown() {
// SnapshotRecordResult recording = WireMock.stopRecording()
// List<StubMapping> mappings = recording.getStubMappings()
// storeMappings(mappings)
// }
// private void recordFromBroker() {
// WireMock.startRecording(WireMock.recordSpec()
// .forTarget("https://test.pact.dius.com.au")
// .extractTextBodiesOver(9999999L)
// .extractBinaryBodiesOver(9999999L)
// .makeStubsPersistent(false))
// }
// private void storeMappings(List<StubMapping> mappings) {
// try {
// File proxiedStubs = new File("target/stubs")
// proxiedStubs.mkdirs()
// for (StubMapping mapping : mappings) {
// File stub = new File(proxiedStubs, "foo" + ".json")
// stub.createNewFile()
// Files.write(stub.toPath(), mapping.toString().getBytes())
// }
// } catch (Exception e) {
// throw new RuntimeException(e)
// }
// }
def "should retrieve pacts from broker using stubrunner options"() throws IOException {
given:
StubRunnerOptions options = new StubRunnerOptionsBuilder()
.withStubRepositoryRoot("pact://https://test.pact.dius.com.au:443")
.withUsername("dXfltyFMgNOFZAxr8io9wJ37iUpY42M")
.withPassword("O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1")
.build()
PactStubDownloader downloader = new PactStubDownloader(options)
when:
Map.Entry<StubConfiguration, File> entry = downloader
.
downloadAndUnpackStubJar(new StubConfiguration("com.example:bobby:+:classifier"))
then:
entry != null
entry.getValue().exists()
File contracts = new File(entry.getValue(), "com/example/bobby/contracts")
contracts.exists()
contracts.list() != null
contracts.list().size() > 0
File mappings = new File(entry.getValue(), "com/example/bobby/mappings")
mappings.exists()
mappings.list() != null
mappings.list().size() > 0
}
}

View File

@@ -1,847 +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.spec.pact
import java.nio.file.Files
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.model.PactSpecVersion
import groovy.json.JsonOutput
import org.skyscreamer.jsonassert.JSONAssert
import spock.lang.Issue
import spock.lang.Specification
import spock.lang.Subject
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.TestGenerator
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
import org.springframework.core.io.Resource
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
/**
* @author Marcin Grzejszczak
* @author Tim Ysewyn
*/
class PactContractConverterSpec extends Specification {
File pactJson = new File(PactContractConverterSpec.getResource("/pact/pact.json").
toURI())
File pact509Json = new File(PactContractConverterSpec.
getResource("/pact/pact_509.json").toURI())
File pactv2Json = new File(PactContractConverterSpec.
getResource("/pact/pact_v2.json").toURI())
File pactv3Json = new File(PactContractConverterSpec.
getResource("/pact/pact_v3.json").toURI())
File pactv3Issue889Json = new File(PactContractConverterSpec.
getResource("/pact/pact_v3_issue_889.json").toURI())
File pactv3MessagingJson = new File(PactContractConverterSpec.
getResource("/pact/pact_v3_messaging.json").toURI())
File pactv3UnsupportedRuleLogicJson = new File(PactContractConverterSpec.
getResource("/pact/pact_v3_unsupported_rule_logic.json").toURI())
File pact1043Json = new File(PactContractConverterSpec.
getResource("/pact/for-test-generation/pact_1043.json").toURI())
File pact1134Json = new File(PactContractConverterSpec.
getResource("/pact/1134/pact_1134.json").toURI())
@Subject
PactContractConverter converter = new PactContractConverter()
def "should accept json files that are pact files"() {
expect:
converter.isAccepted(pactJson)
}
def "should reject json files that are invalid pact files"() {
given:
File invalidPact = new File(PactContractConverterSpec.
getResource("/pact/invalid_pact.json").toURI())
expect:
!converter.isAccepted(invalidPact)
}
def "should convert from pact to contract"() {
given:
Contract expectedContract = Contract.make {
description("a retrieve Mallory request a user with username 'username' and password 'password' exists")
request {
method(GET())
url("/mallory") {
queryParameters {
parameter("name", "ron")
parameter("status", "good")
}
}
headers {
header(contentType(), applicationJson())
}
body(id: "123", method: "create")
bodyMatchers {
jsonPath('$.id', byRegex("[0-9]{3}"))
}
}
response {
status(200)
headers {
header(contentType(), applicationJson())
}
body([[
[email : "rddtGwwWMEhnkAPEmsyE",
id : "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
userName: "AJQrokEGPAVdOHprQpKP"]
]])
bodyMatchers {
jsonPath('$[0][*].email', byType())
jsonPath('$[0][*].id',
byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
jsonPath('$[0]', byType() {
maxOccurrence(5)
})
jsonPath('$[0][*].userName', byType())
}
}
}
when:
Collection<Contract> contracts = converter.convertFrom(pactJson)
then:
contracts == [expectedContract]
}
@Issue("#509")
def "should convert from pact with matching rules to whole body to contract"() {
given:
Contract expectedContract = Contract.make {
description("a request to POST a person provider accepts a new person")
request {
method(POST())
url("/user-service/users")
headers {
header(contentType(), applicationJson())
}
body(firstName: "Arthur", lastName: "Dent")
}
response {
status(201)
headers {
header(contentType(), applicationJsonUtf8())
}
body(firstName: "Arthur", id: 42, lastName: "Dent")
bodyMatchers {
jsonPath('''$.['firstName']''', byType())
jsonPath('''$.['id']''', byType())
jsonPath('''$.['lastName']''', byType())
}
}
}
when:
Collection<Contract> contracts = converter.convertFrom(pact509Json)
then:
contracts == [expectedContract]
}
def "should convert from contract to pact"() {
given:
Collection<Contract> inputContracts = [
Contract.make {
name("my_consumer___my_producer___testname")
description("a retrieve Mallory request")
request {
method(GET())
url("/mallory") {
queryParameters {
parameter("name", "ron")
parameter("status", "good")
}
}
headers {
contentType(applicationJson())
}
body(
id: 123,
method: $(stub(regex("[0][1][2]"))),
something: "foo"
)
bodyMatchers {
jsonPath('$.id', byRegex("[0-9]{3}"))
jsonPath('$.something', byEquality())
}
}
response {
status(200)
headers {
contentType(applicationJson())
}
body([[
[email : "rddtGwwWMEhnkAPEmsyE",
id : "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
number :
$(producer(regex("[0-9]{3}")), consumer(923)),
positiveInteger : 1234567890,
negativeInteger : -1234567890,
positiveDecimalNumber: 123.4567890,
negativeDecimalNumber: -123.4567890,
something : "foo",
userName : "AJQrokEGPAVdOHprQpKP",
nullValue : null]
]])
bodyMatchers {
jsonPath('$[0][*].email', byType())
jsonPath('$[0][*].id',
byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
jsonPath('$[0]', byType() {
minOccurrence(1)
maxOccurrence(5)
})
jsonPath('$[0][*].number', byRegex(number()))
jsonPath('$[0][*].positiveInteger', byRegex(anInteger()))
jsonPath('$[0][*].negativeInteger', byRegex(anInteger()))
jsonPath('$[0][*].positiveDecimalNumber', byRegex(aDouble()))
jsonPath('$[0][*].negativeDecimalNumber', byRegex(aDouble()))
jsonPath('$[0][*].userName', byType())
jsonPath('$[0][*].something', byEquality())
jsonPath('$[0][*].nullValue', byNull())
}
}
}
]
String expectedJson = '''
{
"provider": {
"name": "my_producer"
},
"consumer": {
"name": "my_consumer"
},
"interactions": [
{
"description": "a retrieve Mallory request",
"request": {
"method": "GET",
"path": "\\/mallory",
"query": {
"name": ["ron"],
"status": ["good"]
},
"headers": {
"Content-Type": "application\\/json"
},
"body": {
"id": 123,
"method": "012",
"something": "foo"
},
"matchingRules": {
"body": {
"$.id": {
"matchers": [{
"match": "regex",
"regex": "[0-9]{3}"
}]
},
"$.something": {
"matchers": [{
"match": "equality"
}]
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application\\/json"
},
"body": [
[
{
"email": "rddtGwwWMEhnkAPEmsyE",
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
"number": 923,
"positiveInteger": 1234567890,
"negativeInteger": -1234567890,
"positiveDecimalNumber": 123.4567890,
"negativeDecimalNumber": -123.4567890,
"userName": "AJQrokEGPAVdOHprQpKP",
"something": "foo",
"nullValue": null
}
]
],
"matchingRules": {
"body": {
"$[0][*].email": {
"matchers": [{
"match": "type"
}]
},
"$[0][*].id": {
"matchers": [{
"match": "regex",
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
}]
},
"$[0]": {
"matchers": [{
"match": "type",
"min": 1,
"max": 5
}]
},
"$[0][*].number": {
"matchers": [{
"match": "number"
}]
},
"$[0][*].positiveInteger": {
"matchers": [{
"match": "integer"
}]
},
"$[0][*].negativeInteger": {
"matchers": [{
"match": "integer"
}]
},
"$[0][*].positiveDecimalNumber": {
"matchers": [{
"match": "decimal"
}]
},
"$[0][*].negativeDecimalNumber": {
"matchers": [{
"match": "decimal"
}]
},
"$[0][*].userName": {
"matchers": [{
"match": "type"
}]
},
"$[0][*].something": {
"matchers": [{
"match": "equality"
}]
},
"$[0][*].nullValue": {
"matchers": [{
"match": "null"
}]
}
}
}
}
}
],
"metadata": {
"pact-jvm": {
"version": "4.3.15"
}
}
}
'''
when:
Pact pact = converter.convertTo(inputContracts).get(0)
then:
String actual = JsonOutput.toJson(pact.toMap(PactSpecVersion.V3))
JSONAssert.assertEquals(expectedJson, actual, false)
}
def "should fail to convert from contract to pact when contract has execution property in request"() {
given:
Collection<Contract> inputContracts = [
Contract.make {
request {
method(GET())
url("/mallory")
body(
id: $(c("foo"), p(execute("foo")))
)
}
response {
status(200)
}
}
]
when:
converter.convertTo(inputContracts)
then:
def e = thrown(UnsupportedOperationException)
e.message.contains("execution property")
}
def "should fail to convert from contract to pact when contract has execution property in response"() {
given:
Collection<Contract> inputContracts = [
Contract.make {
request {
method(GET())
url("/mallory")
}
response {
status(200)
body(
id: $(c(execute("foo")), p("foo"))
)
}
}
]
when:
converter.convertTo(inputContracts)
then:
def e = thrown(UnsupportedOperationException)
e.message.contains("execution property")
}
def "should convert contracts from samples to pacts"() {
given:
Resource[] contractResources = new PathMatchingResourcePatternResolver().
getResources("contracts/*.groovy")
Resource[] pactResources = new PathMatchingResourcePatternResolver().
getResources("contracts/*.json")
Map<String, Collection<Contract>> contracts = contractResources.
collectEntries {
[(it.filename): ContractVerifierDslConverter.
convertAsCollection(new File("/"), it.getFile())]
}
Map<String, String> jsonPacts = pactResources.
collectEntries { [(it.filename): it.getFile().text] }
when:
Map<String, Collection<Pact>> pacts = contracts.entrySet().
collectEntries { [(it.key): converter.convertTo(it.value)] }
then:
pacts.entrySet().each {
String convertedPactAsText = JsonOutput.
toJson(it.value[0].toMap(PactSpecVersion.V3))
String pactFileName = it.key.replace("groovy", "json")
println "File name [${it.key}]"
JSONAssert.
assertEquals(jsonPacts.get(pactFileName), convertedPactAsText, true)
}
}
def "should convert contracts from grouped contracts to pacts"() {
given:
List<Contract> contracts = ContractVerifierDslConverter.
convertAsCollection(new File("/"),
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.groovy"))
when:
Collection<Pact> pacts = converter.convertTo(contracts)
then:
pacts.size() == 1
String convertedPactAsText = JsonOutput.
toJson(pacts.first().toMap(PactSpecVersion.V3))
JSONAssert.assertEquals(
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.json").text,
convertedPactAsText, false)
}
def "should convert pacts to strings"() {
given:
List<Contract> contracts = ContractVerifierDslConverter.
convertAsCollection(new File("/"),
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.groovy"))
and:
Collection<Pact> pacts = converter.convertTo(contracts)
when:
Map<String, byte[]> strings = converter.store(pacts)
then:
strings.size() == 1
strings.keySet().first().
startsWith("10-04-pact-consumer_10-05-pact-producer_")
strings.keySet().first().endsWith(".json")
JSONAssert.assertEquals(
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.json").text,
new String(strings.values().first()), false)
}
def "should convert from pact v2 to two SC contracts"() {
given:
Collection<Contract> expectedContracts = [
Contract.make {
description("get all users for max a user with an id named 'user' exists")
request {
method(GET())
url("/idm/user")
}
response {
status(200)
headers {
header(contentType(), applicationJsonUtf8())
}
body([[
[email : "rddtGwwWMEhnkAPEmsyE",
id : "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
userName: "AJQrokEGPAVdOHprQpKP"]
]])
bodyMatchers {
jsonPath('$[0][*].email', byType())
jsonPath('$[0][*].id',
byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
jsonPath('$[0]', byType() {
maxOccurrence(5)
})
jsonPath('$[0][*].userName', byType())
}
}
},
Contract.make {
description("get all users for min a user with an id named 'user' exists")
request {
method(GET())
url("/idm/user")
}
response {
status(200)
headers {
header(contentType(), applicationJsonUtf8())
}
body([[
[email : "DPvAfkCZpOBZWzKYiDMC",
id : "95d0371b-bf30-4943-90a8-8bb1967c4cb2",
userName: "GIUlVKoiLdHLYNKGbcSy"]
]])
bodyMatchers {
jsonPath('$[0][*].email', byType())
jsonPath('$[0][*].id',
byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
jsonPath('$[0]', byType() {
minOccurrence(5)
})
jsonPath('$[0][*].userName', byType())
}
}
}
]
when:
Collection<Contract> contracts = converter.convertFrom(pactv2Json)
then:
contracts == expectedContracts
}
def "should convert from pact v3 to three SC contracts"() {
given:
Collection<Contract> expectedContracts = [
Contract.make {
description("java test interaction with a DSL array body")
request {
method(GET())
url("/")
headers {
header(contentType(), applicationJsonUtf8())
header("Some-Header",
$(c(regex("[a-zA-Z]{9}")), p("someValue")))
header("someHeaderWithJsonContent", '{"issue":"#595"}')
}
}
response {
status(200)
headers {
header(contentType(), applicationJsonUtf8())
header("Some-Header",
$(c("someValue"), p(regex("[a-zA-Z]{9}"))))
header("someHeaderWithJsonContent", '{"issue":"#595"}')
}
body([
[
"dob" : "07/19/2016",
"id" : 8958464620,
"name" : "Rogger the Dogger",
"timestamp" : "2016-07-19T12:14:39",
"nullValue" : null,
"aNumber" : 1234567890,
"positiveInteger" : 1234567890,
"negativeInteger" : -1234567890,
"positiveDecimalNumber": 123.4567890,
"negativeDecimalNumber": -123.4567890
],
[
"dob" : "07/19/2016",
"id" : 4143398442,
"name" : "Cat in the Hat",
"timestamp" : "2016-07-19T12:14:39",
"nullValue" : null,
"aNumber" : 1234567890,
"positiveInteger" : 1234567890,
"negativeInteger" : -1234567890,
"positiveDecimalNumber": 123.4567890,
"negativeDecimalNumber": -123.4567890
]
])
bodyMatchers {
jsonPath('$[0].id', byType())
jsonPath('$[1].id', byType())
jsonPath('$[*].nullValue', byNull())
jsonPath('$[*].aNumber', byRegex(number()))
jsonPath('$[*].positiveInteger', byRegex(anInteger()))
jsonPath('$[*].negativeInteger', byRegex(anInteger()))
jsonPath('$[*].positiveDecimalNumber', byRegex(aDouble()))
jsonPath('$[*].negativeDecimalNumber', byRegex(aDouble()))
}
}
},
Contract.make {
description("test interaction with a array body with templates")
request {
method(GET())
url("/")
}
response {
status(200)
headers {
header(contentType(), applicationJsonUtf8())
}
body([
[
"dob" : "2016-07-19",
"id" : 1943791933,
"name": "ZSAICmTmiwgFFInuEuiK"
],
[
"dob" : "2016-07-19",
"id" : 1943791933,
"name": "ZSAICmTmiwgFFInuEuiK"
],
[
"dob" : "2016-07-19",
"id" : 1943791933,
"name": "ZSAICmTmiwgFFInuEuiK"
]
])
bodyMatchers {
jsonPath('$[2].name', byType())
jsonPath('$[0].id', byType())
jsonPath('$[1].id', byType())
jsonPath('$[2].id', byType())
jsonPath('$[1].name', byType())
jsonPath('$[0].name', byType())
jsonPath('$[0].dob', byDate())
}
}
},
Contract.make {
description("test interaction with an array like matcher")
request {
method(GET())
url("/")
}
response {
status(200)
headers {
header(contentType(), applicationJsonUtf8())
}
body([
"data": [
"array1": [[
"dob" : "2016-07-19",
"id" : 1600309982,
"name": "FVsWAGZTFGPLhWjLuBOd"
]],
"array2": [[
"address": "127.0.0.1",
"name" : "jvxrzduZnwwxpFYrQnpd"
]],
"array3": [[
[
"itemCount": 652571349
]
]]
],
"id" : 7183997828
])
bodyMatchers {
jsonPath('$.data.array3[0]', byType() {
maxOccurrence(5)
})
jsonPath('$.data.array1', byType() {
minOccurrence(0)
})
jsonPath('$.data.array2', byType() {
minOccurrence(1)
})
jsonPath('$.id', byType())
jsonPath('$.data.array2[*].name', byType())
jsonPath('$.data.array2[*].address',
byRegex("(\\d{1,3}\\.)+\\d{1,3}"))
jsonPath('$.data.array1[*].name', byType())
jsonPath('$.data.array1[*].id', byType())
}
}
}
]
when:
Collection<Contract> contracts = converter.convertFrom(pactv3Json)
then:
contracts == expectedContracts
}
@Issue("#889")
def "should not throw an exception when parsing Pact file with different matchers"() {
when:
converter.convertFrom(pactv3Issue889Json)
then:
noExceptionThrown()
}
def "should convert from pact v3 messaging to one SC message contract"() {
given:
Collection<Contract> expectedContracts = [
Contract.make {
description()
label 'message sent to activemq:output'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo 'activemq:output'
body([
bookName: "foo"
])
headers {
header('BOOK-NAME', 'foo')
header(messagingContentType(), applicationJson())
}
bodyMatchers {
jsonPath('$.bookName', byType())
}
}
}
]
when:
Collection<Contract> contracts = converter.convertFrom(pactv3MessagingJson)
then:
contracts == expectedContracts
}
def "should fail to convert a pact v3 contract with unsupported rule logic"() {
when:
converter.convertFrom(pactv3UnsupportedRuleLogicJson)
then:
def e = thrown(UnsupportedOperationException)
e.message.
contains("Currently only the AND combination rule logic is supported")
}
@Issue("1043")
def "should generate a test from pact and not contain a check for an empty array"() {
given:
File output = Files.createTempDirectory("pact").toFile()
output.mkdirs()
when:
new TestGenerator(new ContractVerifierConfigProperties(contractsDslDir: pact1043Json.parentFile, generatedTestSourcesDir: output, generatedTestResourcesDir: output, basePackageForTests: "example")).generate()
then:
File generatedTest = new File(output, "example/ContractVerifierTest.java")
String generatedTestText = generatedTest.text
!generatedTestText.contains('''assertThatJson(parsedJson).array().array("['authors']").isEmpty()''')
}
@Issue("1134")
def "should work properly with cookies when reading a pact file as contract"() {
given:
File output = Files.createTempDirectory("pact").toFile()
output.mkdirs()
when:
new TestGenerator(new ContractVerifierConfigProperties(contractsDslDir: pact1134Json.parentFile, generatedTestSourcesDir: output, generatedTestResourcesDir: output, basePackageForTests: "example")).generate()
then:
File generatedTest = new File(output, "example/ContractVerifierTest.java")
String generatedTestText = generatedTest.text
generatedTestText.contains('''cookie("a", "1")''')
generatedTestText.contains('''cookie("b", "2")''')
generatedTestText.contains('''assertThat(response.cookie("c")).isNotNull()''')
generatedTestText.contains('''assertThat(response.cookie("c")).isEqualTo("1")''')
generatedTestText.contains('''assertThat(response.cookie("d")).isNotNull()''')
generatedTestText.contains('''assertThat(response.cookie("d")).isEqualTo("2")''')
}
@Issue("1134")
def "should work properly with cookies when converting contract to pact"() {
given:
Collection<Contract> expectedContracts = [
Contract.make {
description("A successful Api GET call get")
request {
method(GET())
url("/books")
// cause Pact stores cookies in headers
headers {
}
cookies {
cookie("a", "1")
cookie("b", "2")
}
}
response {
status(200)
// cause Pact stores cookies in headers
headers {
}
cookies {
cookie("c", "1")
cookie("d", "2")
}
}
}
]
when:
Collection<Contract> contracts = converter.convertFrom(pact1134Json)
then:
contracts == expectedContracts
}
@Issue("1277")
def "should work properly with json body"() {
when:
converter.convertTo([
Contract.make {
request {
method 'PUT'
url '/api/admins/1'
body('''{
"username" : "username",
"password" : "password",
"roles" : [ "ADMIN" ]
}''')
headers {
header('''Content-Type''', '''application/json;charset=UTF-8''')
}
}
response {
status 200
body('''{
"admin" : {
"adminId" : 1,
"username" : "username",
"roles" : [ "ADMIN" ]
}
}''')
headers {
header('''Content-Type''', '''application/json;charset=UTF-8''')
}
}
}
])
then:
noExceptionThrown()
}
}
// file creator
/*
pacts.entrySet().each {
new File("target/${it.key.replace("groovy", "json")}").text = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V3))
}
*/

View File

@@ -1,51 +0,0 @@
/*
* 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", ""));
}
}

View File

@@ -1,106 +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 contracts.grouped
import org.springframework.cloud.contract.spec.Contract
[
Contract.make {
description("""
Represents a successful scenario of getting a beer
```
given:
client is old enough
when:
he applies for a beer
then:
we'll grant him the beer
```
""")
request {
method POST()
name("10-04-pact-consumer___10-05-pact-producer___too young")
url '/check'
body(
age: 60
)
headers {
contentType(applicationJson())
}
bodyMatchers {
jsonPath('$.age', byRegex(regex("[2-9][0-9]")))
}
}
response {
status 200
body("""
{
"status": "OK"
}
""")
headers {
contentType(applicationJson())
}
}
},
Contract.make {
description("""
Represents an unsuccessful scenario of getting a beer
```
given:
client is not old enough
when:
he applies for a beer
then:
we'll NOT grant him the beer
```
""")
request {
method POST()
name("10-04-pact-consumer___10-05-pact-producer___old enough")
url '/check'
body(
age: 10
)
headers {
contentType(applicationJson())
}
bodyMatchers {
jsonPath('$.age', byRegex(regex("[0-1][0-9]")))
}
}
response {
status 200
body("""
{
"status": "NOT_OK"
}
""")
headers {
contentType(applicationJson())
}
bodyMatchers {
jsonPath('$.status', byType())
}
}
}
]

View File

@@ -1,153 +0,0 @@
{
"provider": {
"name": "10-05-pact-producer"
},
"consumer": {
"name": "10-04-pact-consumer"
},
"interactions": [
{
"description": "\nRepresents an unsuccessful scenario of getting a beer\n\n```\ngiven:\n\tclient is not old enough\nwhen:\n\the applies for a beer\nthen:\n\twe'll NOT grant him the beer\n```\n\n",
"request": {
"method": "POST",
"path": "/check",
"headers": {
"Content-Type": "application/json"
},
"body": {
"age": 10
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.age": {
"matchers": [
{
"match": "regex",
"regex": "[0-1][0-9]"
}
],
"combine": "AND"
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"status": "NOT_OK"
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.status": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
},
{
"description": "\nRepresents a successful scenario of getting a beer\n\n```\ngiven:\n\tclient is old enough\nwhen:\n\the applies for a beer\nthen:\n\twe'll grant him the beer\n```\n\n",
"request": {
"method": "POST",
"path": "/check",
"headers": {
"Content-Type": "application/json"
},
"body": {
"age": 60
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.age": {
"matchers": [
{
"match": "regex",
"regex": "[2-9][0-9]"
}
],
"combine": "AND"
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"status": "OK"
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.status": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
}
],
"metadata": {
"pact-jvm": {
"version": "4.3.15"
}
}
}

View File

@@ -1,80 +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 contracts
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'POST'
url '/'
body([
someInteger : $(c(anyInteger()), p(1234567890)),
someDecimal : $(c(anyDouble()), p(123.123)),
someHex : $(c(anyHex()), p('DEADC0DE')),
someAlphaNumeric:
$(c(anyAlphaNumeric()), p('Some alpha numeric string with 1234567890')),
someUUID : $(c(anyUuid()), p('00000000-0000-0000-0000-000000000000')),
someDate : $(c(anyDate()), p('2018-03-26')),
someTime : $(c(anyTime()), p('13:37:00')),
someDateTime : $(c(anyDateTime()), p('2018-03-26 13:37:00')),
someBoolean : $(c(anyBoolean()), p('true')),
someRegex : $(c(regex('[0-9]{10}')), p(1234567890))
])
headers {
contentType('application/json')
}
}
response {
status OK()
body([
someInteger : $(c(1234567890), p(anyInteger())),
someDecimal : $(c(123.123), p(anyDouble())),
someHex : $(c('DEADC0DE'), p(anyHex())),
someAlphaNumeric:
$(c('Some alpha numeric string with 1234567890'), p(anyAlphaNumeric())),
someUUID : $(c('00000000-0000-0000-0000-000000000000'), p(anyUuid())),
someDate : $(c('2018-03-26'), p(anyDate())),
someTime : $(c('13:37:00'), p(anyTime())),
someDateTime : $(c('2018-03-26 13:37:00'), p(anyDateTime())),
someBoolean : $(c('true'), p(anyBoolean())),
someRegex : $(c(1234567890), p(regex('[0-9]{10}')))
])
headers {
contentType('application/json')
}
}
metadata([
pact: [
providerStates:
[
[
name: "someState1",
params: [
id: 1,
value: "someValue1"
]
],
[
name: "someState2",
params: [
id: 2,
value: "someValue2"
]
]
]
]
])
}

View File

@@ -1,296 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "POST",
"path": "/",
"headers": {
"Content-Type": "application/json"
},
"body": {
"someInteger": 1234567890,
"someDecimal": 123.123,
"someHex": "DEADC0DE",
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
"someUUID": "00000000-0000-0000-0000-000000000000",
"someDate": "2018-03-26",
"someTime": "13:37:00",
"someDateTime": "2018-03-26 13:37:00",
"someBoolean": "true",
"someRegex": 1234567890
},
"generators": {
"body": {
"$.someInteger": {
"type": "RandomInt",
"min": 0,
"max": 2147483647
},
"$.someDecimal": {
"type": "RandomDecimal",
"digits": 10
},
"$.someHex": {
"type": "RandomHexadecimal",
"digits": 10
},
"$.someAlphaNumeric": {
"type": "RandomString",
"size": 10
},
"$.someUUID": {
"type": "Uuid"
},
"$.someDate": {
"type": "Date"
},
"$.someTime": {
"type": "Time"
},
"$.someDateTime": {
"type": "DateTime"
},
"$.someBoolean": {
"type": "RandomBoolean"
},
"$.someRegex": {
"type": "Regex",
"regex": "[0-9]{10}"
}
}
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.someHex": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someAlphaNumeric": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someUUID": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someDate": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someTime": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someDateTime": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someBoolean": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"someInteger": 1234567890,
"someDecimal": 123.123,
"someHex": "DEADC0DE",
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
"someUUID": "00000000-0000-0000-0000-000000000000",
"someDate": "2018-03-26",
"someTime": "13:37:00",
"someDateTime": "2018-03-26 13:37:00",
"someBoolean": "true",
"someRegex": 1234567890
},
"generators": {
"body": {
"$.someInteger": {
"type": "RandomInt",
"min": 0,
"max": 2147483647
},
"$.someDecimal": {
"type": "RandomDecimal",
"digits": 10
},
"$.someHex": {
"type": "RandomHexadecimal",
"digits": 10
},
"$.someAlphaNumeric": {
"type": "RandomString",
"size": 10
},
"$.someUUID": {
"type": "Uuid"
},
"$.someDate": {
"type": "Date"
},
"$.someTime": {
"type": "Time"
},
"$.someDateTime": {
"type": "DateTime"
},
"$.someBoolean": {
"type": "RandomBoolean"
},
"$.someRegex": {
"type": "Regex",
"regex": "[0-9]{10}"
}
}
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.someHex": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someAlphaNumeric": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someUUID": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someDate": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someTime": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someDateTime": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.someBoolean": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
},
"providerStates": [
{
"name": "someState1",
"params": {
"id": 1,
"value": "someValue1"
}
},
{
"name": "someState2",
"params": {
"id": 2,
"value": "someValue2"
}
}
]
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.3.15"
}
}
}

View File

@@ -1,82 +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 contracts
org.springframework.cloud.contract.spec.Contract.make {
request { // (1)
method 'PUT' // (2)
url '/fraudcheck' // (3)
body([ // (4)
clientId : $(c(regex('[0-9]{10}')), p("8532032713")),
loanAmount: 99999
])
headers { // (5)
contentType('application/vnd.fraud.v1+json')
}
}
response { // (6)
status OK() // (7)
body([ // (8)
fraudCheckStatus: "FRAUD",
rejectionReason : "Amount too high"
])
headers { // (9)
contentType('application/vnd.fraud.v1+json')
}
}
}
/*
Since we don't want to force on the user to hardcode values of fields that are dynamic
(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's
value in a `$(...)` or `value(...)` and provide a dynamic value of a field then
the concrete value will be generated for you. If you want to be really explicit about
which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation.
That way what's present in the `consumer` section will end up in the produced stub. What's
there in the `producer` will end up in the autogenerated test. If you provide only the
regular expression side without the concrete value then Spring Cloud Contract will generate one for you.
From the Consumer perspective, when shooting a request in the integration test:
(1) - If the consumer sends a request
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
(6) - then the response will be sent with
(7) - status equal `200`
(8) - and JSON body equal to
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
From the Producer perspective, in the autogenerated producer-side test:
(1) - A request will be sent to the producer
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
(6) - then the test will assert if the response has been sent with
(7) - status equal `200`
(8) - and JSON body equal to
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*`
*/

View File

@@ -1,104 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "PUT",
"path": "/fraudcheck",
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"clientId": "8532032713",
"loanAmount": 99999
},
"generators": {
"body": {
"$.clientId": {
"type": "Regex",
"regex": "[0-9]{10}"
}
}
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.clientId": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"fraudCheckStatus": "FRAUD",
"rejectionReason": "Amount too high"
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.fraudCheckStatus": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.rejectionReason": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.3.15"
}
}
}

View File

@@ -1,49 +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 contracts
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'PUT'
url '/fraudcheck'
body("""
{
"clientId":"${
value(consumer(regex('[0-9]{10}')), producer('1234567890'))
}",
"loanAmount":123.123
}
"""
)
headers {
contentType("application/vnd.fraud.v1+json")
}
}
response {
status OK()
body(
fraudCheckStatus: "OK",
rejectionReason: $(consumer(null),
producer(execute('assertThatRejectionReasonIsNull($it)')))
)
headers {
contentType("application/vnd.fraud.v1+json")
}
}
}

View File

@@ -1,96 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "PUT",
"path": "/fraudcheck",
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"clientId": "1234567890",
"loanAmount": 123.123
},
"generators": {
"body": {
"$.clientId": {
"type": "Regex",
"regex": "[0-9]{10}"
}
}
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.clientId": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"fraudCheckStatus": "OK",
"rejectionReason": null
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
}
],
"combine": "AND"
}
},
"body": {
"$.fraudCheckStatus": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.3.15"
}
}
}

View File

@@ -1,53 +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 contracts
import org.springframework.cloud.contract.spec.Contract
[
Contract.make {
request {
name "should count all frauds"
method GET()
url '/frauds'
}
response {
status OK()
body([
count: 200
])
headers {
contentType("application/vnd.fraud.v1+json")
}
}
},
Contract.make {
request {
method GET()
url '/drunks'
}
response {
status OK()
body([
count: 100
])
headers {
contentType("application/vnd.fraud.v1+json")
}
}
}
]

View File

@@ -1,76 +0,0 @@
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "",
"request": {
"method": "GET",
"path": "/frauds"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"count": 200
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
}
],
"combine": "AND"
}
}
}
}
},
{
"description": "",
"request": {
"method": "GET",
"path": "/drunks"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/vnd.fraud.v1+json"
},
"body": {
"count": 100
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
}
],
"combine": "AND"
}
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.3.15"
}
}
}

View File

@@ -1,36 +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 contracts
import org.springframework.cloud.contract.spec.Contract
[
Contract.make {
label 'some_label'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('activemq:output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
messagingContentType(applicationJson())
}
}
}
]

View File

@@ -1,45 +0,0 @@
{
"consumer": {
"name": "Consumer"
},
"provider": {
"name": "Provider"
},
"messages": [
{
"description": "message sent to activemq:output",
"metaData": {
"BOOK-NAME": "foo",
"contentType": "application/json"
},
"contents": {
"bookName": "foo"
},
"providerStates": [
{
"name": "bookReturnedTriggered()"
}
],
"matchingRules": {
"body": {
"$.bookName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.3.15"
}
}
}

View File

@@ -1,39 +0,0 @@
{
"provider": {
"name": "cookie-service"
},
"consumer": {
"name": "cookie-consumer"
},
"interactions": [
{
"description": "A successful Api GET call",
"request": {
"method": "GET",
"path": "/books",
"headers": {
"Cookie": "a=1;b=2"
}
},
"response": {
"status": 200,
"headers": {
"Cookie": "c=1;d=2"
}
},
"providerStates": [
{
"name": "get"
}
]
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.13"
}
}
}

View File

@@ -1,115 +0,0 @@
{
"provider": {
"name": "book-catalog-service"
},
"consumer": {
"name": "books-client-catalog-rest-consumer"
},
"interactions": [
{
"description": "A successful Api GET call",
"request": {
"method": "GET",
"path": "/books",
"headers": {
"Content-Type": "application/json;charset=UTF-8"
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": [
{
"isbn": "978-3-86680-192-9",
"id": 100,
"title": "A book",
"authors": [
{
"firstName": "string",
"lastName": "string"
}
]
}
],
"matchingRules": {
"body": {
"$[0].id": {
"matchers": [
{
"match": "integer"
}
],
"combine": "AND"
},
"$[0].title": {
"matchers": [
{
"match": "regex",
"regex": ".*"
}
],
"combine": "AND"
},
"$[0].authors[0].firstName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$[0].authors[0].lastName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$[0].isbn": {
"matchers": [
{
"match": "regex",
"regex": "[0-9]{3}-[0-9]{1}-[0-9]{5}-[0-9]{3}-[0-9]{1}"
}
],
"combine": "AND"
}
}
},
"generators": {
"body": {
"$[0].id": {
"type": "RandomInt",
"min": 0,
"max": 2147483647
},
"$[0].authors[0].firstName": {
"type": "RandomString",
"size": 20
},
"$[0].authors[0].lastName": {
"type": "RandomString",
"size": 20
}
}
}
},
"providerStates": [
{
"name": "get"
}
]
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.13"
}
}
}

View File

@@ -1,62 +0,0 @@
{
"provider": {
"name": "Alice Service"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "a retrieve Mallory request",
"provider_state": "a user with username 'username' and password 'password' exists",
"request": {
"method": "GET",
"path": "/mallory",
"query": "name=ron&status=good",
"body": {
"id": "123",
"method": "create"
},
"headers": {
"Content-Type": "application/json"
},
"matchingRules": {
"$.body.id": {
"match": "regex",
"regex": "[0-9]{3}"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": [
[
{
"email": "rddtGwwWMEhnkAPEmsyE",
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
"userName": "AJQrokEGPAVdOHprQpKP"
}
]
],
"matchingRules": {
"$.body[0][*].email": {
"match": "type"
},
"$.body[0][*].id": {
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
},
"$.body[0]": {
"max": 5,
"match": "type"
},
"$.body[0][*].userName": {
"match": "type"
}
}
}
}
]
}

View File

@@ -1,46 +0,0 @@
{
"consumer": {
"name": "ui"
},
"provider": {
"name": "userservice"
},
"interactions": [
{
"description": "a request to POST a person",
"providerState": "provider accepts a new person",
"request": {
"method": "POST",
"path": "/user-service/users",
"headers": {
"Content-Type": "application/json"
},
"body": {
"firstName": "Arthur",
"lastName": "Dent"
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": {
"id": 42,
"firstName": "Arthur",
"lastName": "Dent"
},
"matchingRules": {
"$.body": {
"match": "type"
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "2.0.0"
}
}
}

View File

@@ -1,94 +0,0 @@
{
"provider": {
"name": "266_provider"
},
"consumer": {
"name": "test_consumer"
},
"interactions": [
{
"description": "get all users for max",
"request": {
"method": "GET",
"path": "/idm/user"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": [
[
{
"email": "rddtGwwWMEhnkAPEmsyE",
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
"userName": "AJQrokEGPAVdOHprQpKP"
}
]
],
"matchingRules": {
"$.body[0][*].email": {
"match": "type"
},
"$.body[0][*].id": {
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
},
"$.body[0]": {
"max": 5,
"match": "type"
},
"$.body[0][*].userName": {
"match": "type"
}
}
},
"providerState": "a user with an id named 'user' exists"
},
{
"description": "get all users for min",
"request": {
"method": "GET",
"path": "/idm/user"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": [
[
{
"email": "DPvAfkCZpOBZWzKYiDMC",
"id": "95d0371b-bf30-4943-90a8-8bb1967c4cb2",
"userName": "GIUlVKoiLdHLYNKGbcSy"
}
]
],
"matchingRules": {
"$.body[0][*].email": {
"match": "type"
},
"$.body[0][*].id": {
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
},
"$.body[0]": {
"min": 5,
"match": "type"
},
"$.body[0][*].userName": {
"match": "type"
}
}
},
"providerState": "a user with an id named 'user' exists"
}
],
"metadata": {
"pact-specification": {
"version": "2.0.0"
},
"pact-jvm": {
"version": "3.2.11"
}
}
}

View File

@@ -1,330 +0,0 @@
{
"provider": {
"name": "test_provider_array"
},
"consumer": {
"name": "test_consumer_array"
},
"interactions": [
{
"description": "java test interaction with a DSL array body",
"request": {
"method": "GET",
"path": "/",
"headers": {
"Content-Type": "application/json;charset=UTF-8",
"Some-Header": "someValue",
"someHeaderWithJsonContent": "{\"issue\":\"#595\"}"
},
"matchingRules": {
"header": {
"Some-Header": {
"matchers": [
{
"match": "regex",
"regex": "[a-zA-Z]{9}"
}
]
}
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8",
"Some-Header": "someValue",
"someHeaderWithJsonContent": "{\"issue\":\"#595\"}"
},
"body": [
{
"dob": "07/19/2016",
"id": 8958464620,
"name": "Rogger the Dogger",
"timestamp": "2016-07-19T12:14:39",
"nullValue": null,
"aNumber": 1234567890,
"positiveInteger": 1234567890,
"negativeInteger": -1234567890,
"positiveDecimalNumber": 123.4567890,
"negativeDecimalNumber": -123.4567890
},
{
"dob": "07/19/2016",
"id": 4143398442,
"name": "Cat in the Hat",
"timestamp": "2016-07-19T12:14:39",
"nullValue": null,
"aNumber": 1234567890,
"positiveInteger": 1234567890,
"negativeInteger": -1234567890,
"positiveDecimalNumber": 123.4567890,
"negativeDecimalNumber": -123.4567890
}
],
"matchingRules": {
"header": {
"Some-Header": {
"matchers": [
{
"match": "regex",
"regex": "[a-zA-Z]{9}"
}
]
}
},
"body": {
"$[0].id": {
"matchers": [
{
"match": "type"
}
]
},
"$[1].id": {
"matchers": [
{
"match": "type"
}
]
},
"$[*].nullValue": {
"matchers": [
{
"match": "null"
}
]
},
"$[*].aNumber": {
"matchers": [
{
"match": "number"
}
]
},
"$[*].positiveInteger": {
"matchers": [
{
"match": "integer"
}
]
},
"$[*].negativeInteger": {
"matchers": [
{
"match": "integer"
}
]
},
"$[*].positiveDecimalNumber": {
"matchers": [
{
"match": "decimal"
}
]
},
"$[*].negativeDecimalNumber": {
"matchers": [
{
"match": "decimal"
}
]
}
}
}
}
},
{
"description": "test interaction with a array body with templates",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": [
{
"dob": "2016-07-19",
"id": 1943791933,
"name": "ZSAICmTmiwgFFInuEuiK"
},
{
"dob": "2016-07-19",
"id": 1943791933,
"name": "ZSAICmTmiwgFFInuEuiK"
},
{
"dob": "2016-07-19",
"id": 1943791933,
"name": "ZSAICmTmiwgFFInuEuiK"
}
],
"matchingRules": {
"body": {
"$[2].name": {
"matchers": [
{
"match": "type"
}
]
},
"$[0].id": {
"matchers": [
{
"match": "type"
}
]
},
"$[1].id": {
"matchers": [
{
"match": "type"
}
]
},
"$[2].id": {
"matchers": [
{
"match": "type"
}
]
},
"$[1].name": {
"matchers": [
{
"match": "type"
}
]
},
"$[0].name": {
"matchers": [
{
"match": "type"
}
]
},
"$[0].dob": {
"matchers": [
{
"date": "yyyy-MM-dd"
}
]
}
}
}
}
},
{
"description": "test interaction with an array like matcher",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": {
"data": {
"array1": [
{
"dob": "2016-07-19",
"id": 1600309982,
"name": "FVsWAGZTFGPLhWjLuBOd"
}
],
"array2": [
{
"address": "127.0.0.1",
"name": "jvxrzduZnwwxpFYrQnpd"
}
],
"array3": [
[
{
"itemCount": 652571349
}
]
]
},
"id": 7183997828
},
"matchingRules": {
"body": {
"$.data.array3[0]": {
"matchers": [
{
"max": 5,
"match": "type"
}
]
},
"$.data.array1": {
"matchers": [
{
"min": 0,
"match": "type"
}
]
},
"$.data.array2": {
"matchers": [
{
"min": 1,
"match": "type"
}
]
},
"$.id": {
"matchers": [
{
"match": "type"
}
]
},
"$.data.array2[*].name": {
"matchers": [
{
"match": "type"
}
]
},
"$.data.array2[*].address": {
"matchers": [
{
"regex": "(\\d{1,3}\\.)+\\d{1,3}"
}
]
},
"$.data.array1[*].name": {
"matchers": [
{
"match": "type"
}
]
},
"$.data.array1[*].id": {
"matchers": [
{
"match": "type"
}
]
}
}
}
}
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.2.11"
}
}
}

View File

@@ -1,83 +0,0 @@
{
"consumer": {
"name": "order-manager"
},
"provider": {
"name": "lineitem-worker"
},
"messages": [
{
"description": "A message sent to order-exchange",
"metaData": {
"sentTo": "order-exchange"
},
"contents": {
"lineItemId": "string",
"orderId": "XXX-XXXXXX-000000-0000C",
"timestamp": 1550500138678,
"marketId": "MRKTDE",
"status": "PROCESSED"
},
"providerStates": [
{
"name": "statusMessageCreated()"
}
],
"matchingRules": {
"body": {
"$.timestamp": {
"matchers": [
{
"match": "number"
}
],
"combine": "AND"
},
"$.lineItemId": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.orderId": {
"matchers": [
{
"match": "regex",
"regex": "[A-Z]{3}-[A-Z]{6}-[0-9]{6}-[0-9A-F]{5}"
}
],
"combine": "AND"
},
"$.marketId": {
"matchers": [
{
"match": "regex",
"regex": "[A-Z]{6}"
}
],
"combine": "AND"
},
"$.status": {
"matchers": [
{
"match": "regex",
"regex": "[A-Z,_]*"
}
],
"combine": "AND"
}
}
}
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.13"
}
}
}

View File

@@ -1,46 +0,0 @@
{
"consumer": {
"name": "Consumer"
},
"provider": {
"name": "Provider"
},
"messages": [
{
"description": "message sent to activemq:output",
"metaData": {
"sentTo": "activemq:output",
"BOOK-NAME": "foo",
"contentType": "application/json"
},
"contents": {
"bookName": "foo"
},
"providerStates": [
{
"name": "bookReturnedTriggered()"
}
],
"matchingRules": {
"body": {
"$.bookName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.13"
}
}
}

View File

@@ -1,51 +0,0 @@
{
"provider": {
"name": "test_unsupported_rule_logic"
},
"consumer": {
"name": "test_unsupported_rule_logic"
},
"interactions": [
{
"description": "test unsupported rule logic",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json;charset=UTF-8"
},
"body": [
{
"optionalField": 1234567890
}
],
"matchingRules": {
"body": {
"$[*].optionalField": {
"matchers": [
{
"match": "integer"
},
{
"match": "null"
}
],
"combine": "OR"
}
}
}
}
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.2.11"
}
}
}