Pact Contract (#188)
with this change we're providing support for Pact based contracts. No longer do you have to set up your contracts using the Groovy DSL. In the same way as with the DSL you can use the Pact contracts to generate tests and the stubs on the producer side. fixes #96
This commit is contained in:
committed by
GitHub
parent
da6046f342
commit
22a5e44471
@@ -19,6 +19,7 @@
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-contract-converters</module>
|
||||
<module>spring-cloud-contract-spec-pact</module>
|
||||
<module>spring-cloud-contract-maven-plugin</module>
|
||||
<module>spring-cloud-contract-gradle-plugin</module>
|
||||
</modules>
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig
|
||||
import org.springframework.cloud.contract.verifier.file.ContractFileScanner
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata
|
||||
import org.springframework.cloud.contract.verifier.util.NamesUtil
|
||||
import org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
@@ -64,8 +65,12 @@ class RecursiveFilesConverter {
|
||||
}
|
||||
contracts.asMap().entrySet().each { entry ->
|
||||
entry.value.each { ContractMetadata contract ->
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Will create a stub for contract [${contract}]")
|
||||
}
|
||||
File sourceFile = contract.path.toFile()
|
||||
StubGenerator stubGenerator = holder.converterForName(sourceFile.name);
|
||||
StubGenerator stubGenerator = contract.convertedContract ? holder.firstOrDefault(new DslToWireMockClientConverter()) :
|
||||
holder.converterForName(sourceFile.name)
|
||||
try {
|
||||
String path = sourceFile.path
|
||||
if (properties.isExcludeBuildFolders() && (matchesPath(path, "target") || matchesPath(path, "build"))) {
|
||||
@@ -78,7 +83,11 @@ class RecursiveFilesConverter {
|
||||
return
|
||||
}
|
||||
int contractsSize = contract.convertedContract.size()
|
||||
Map<Contract, String> convertedContent = stubGenerator.convertContents(entry.key.last().toString(), contract)
|
||||
def entryKey = entry.key
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Stub Generator [${stubGenerator}] will convert contents of [${entryKey}]")
|
||||
}
|
||||
Map<Contract, String> convertedContent = stubGenerator.convertContents(entryKey.last().toString(), contract)
|
||||
if (!convertedContent) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -25,4 +25,8 @@ class StubGeneratorProvider {
|
||||
StubGenerator converterForName(String fileName) {
|
||||
return this.converters.find { it.canHandleFileName(fileName) }
|
||||
}
|
||||
|
||||
StubGenerator firstOrDefault(StubGenerator defaultStubGenerator) {
|
||||
return this.converters.empty ? defaultStubGenerator : this.converters.first()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +369,16 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -16,20 +16,21 @@
|
||||
*/
|
||||
package org.springframework.cloud.contract.maven.verifier;
|
||||
|
||||
import static io.takari.maven.testing.TestMavenRuntime.newParameter;
|
||||
import static io.takari.maven.testing.TestResources.assertFilesNotPresent;
|
||||
import static io.takari.maven.testing.TestResources.assertFilesPresent;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import io.takari.maven.testing.TestMavenRuntime;
|
||||
import io.takari.maven.testing.TestResources;
|
||||
|
||||
import static io.takari.maven.testing.TestMavenRuntime.newParameter;
|
||||
import static io.takari.maven.testing.TestResources.assertFilesNotPresent;
|
||||
import static io.takari.maven.testing.TestResources.assertFilesPresent;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
public class PluginUnitTest {
|
||||
|
||||
@Rule
|
||||
@@ -256,4 +257,19 @@ public class PluginUnitTest {
|
||||
assertFilesPresent(basedir, "target/stubs/contracts/consumer1/Messaging.groovy");
|
||||
assertFilesPresent(basedir, "target/stubs/contracts/pom.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGenerateContractTestsForPactAndMaintainIndents() throws Exception {
|
||||
File basedir = this.resources.getBasedir("pact");
|
||||
|
||||
this.maven.executeMojo(basedir, "generateTests");
|
||||
|
||||
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 = FileUtils.readFileToString(test);
|
||||
int countOccurrencesOf = StringUtils
|
||||
.countOccurrencesOf(testContents, "\t\tMockMvcRequestSpecification");
|
||||
then(countOccurrencesOf).isEqualTo(4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
|
||||
Copyright 2013-2016 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
|
||||
|
||||
http://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="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>1.1.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-spec-pact</artifactId>
|
||||
<version>${spring.cloud.contract.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<version>2.4.18</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>1.1.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Contract Spec Pact</name>
|
||||
<description>Spring Cloud Contract Spec Pact</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-verifier</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-nio</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</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>info.solidsoft.spock</groupId>
|
||||
<artifactId>spock-global-unroll</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>compile</goal>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,318 @@
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.BasePact
|
||||
import au.com.dius.pact.model.Consumer
|
||||
import au.com.dius.pact.model.Interaction
|
||||
import au.com.dius.pact.model.OptionalBody
|
||||
import au.com.dius.pact.model.Pact
|
||||
import au.com.dius.pact.model.PactReader
|
||||
import au.com.dius.pact.model.Provider
|
||||
import au.com.dius.pact.model.Request
|
||||
import au.com.dius.pact.model.RequestResponseInteraction
|
||||
import au.com.dius.pact.model.RequestResponsePact
|
||||
import au.com.dius.pact.model.Response
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.transform.CompileStatic
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.ContractConverter
|
||||
import org.springframework.cloud.contract.spec.internal.BodyMatchers
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Headers
|
||||
import org.springframework.cloud.contract.spec.internal.MatchingType
|
||||
import org.springframework.cloud.contract.spec.internal.QueryParameters
|
||||
import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
|
||||
/**
|
||||
* Converter of JSON PACT file
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@CompileStatic
|
||||
class PactContractConverter implements ContractConverter<Pact> {
|
||||
|
||||
private static final String MATCH_KEY = "match"
|
||||
private static final String REGEX_KEY = "regex"
|
||||
private static final String MAX_KEY = "max"
|
||||
private static final String MIN_KEY = "min"
|
||||
|
||||
@Override
|
||||
boolean isAccepted(File file) {
|
||||
try {
|
||||
PactReader.loadPact(file)
|
||||
return true
|
||||
} catch (Exception e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertFrom(File file) {
|
||||
Pact pact = PactReader.loadPact(file)
|
||||
List<Interaction> interactions = pact.interactions
|
||||
return interactions.collect { Interaction interaction ->
|
||||
Contract.make {
|
||||
if (interaction instanceof RequestResponseInteraction) {
|
||||
RequestResponseInteraction requestResponseInteraction = (RequestResponseInteraction) interaction
|
||||
description("$requestResponseInteraction.description${providerState(interaction)}")
|
||||
request {
|
||||
method(requestResponseInteraction.request.method)
|
||||
if (requestResponseInteraction.request.query) {
|
||||
url(requestResponseInteraction.request.path) {
|
||||
queryParameters {
|
||||
requestResponseInteraction.request.query.each { String key, List<String> value ->
|
||||
value.each { String singleValue ->
|
||||
parameter(key, singleValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
url(requestResponseInteraction.request.path)
|
||||
}
|
||||
if (requestResponseInteraction.request.headers) {
|
||||
headers {
|
||||
requestResponseInteraction.request.headers.each { String key, String value ->
|
||||
header(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requestResponseInteraction.request.body.state == OptionalBody.State.PRESENT) {
|
||||
def parsedBody = BasePact.parseBody(requestResponseInteraction.request)
|
||||
if (parsedBody instanceof Map) {
|
||||
body(parsedBody as Map)
|
||||
} else if (parsedBody instanceof List) {
|
||||
body(parsedBody as List)
|
||||
} else {
|
||||
body(parsedBody.toString())
|
||||
}
|
||||
}
|
||||
if (requestResponseInteraction.request?.matchingRules) {
|
||||
stubMatchers {
|
||||
requestResponseInteraction.request.matchingRules.each { String key, Map<String, Object> value ->
|
||||
String keyFromBody = toKeyStartingFromBody(key)
|
||||
if (value.containsKey(MATCH_KEY)) {
|
||||
MatchingType matchingType = MatchingType.valueOf((value.get(MATCH_KEY) as String).toUpperCase())
|
||||
switch (matchingType) {
|
||||
case MatchingType.EQUALITY:
|
||||
// equality is checked by default in the standard way
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
jsonPath(keyFromBody, byDate())
|
||||
break
|
||||
case MatchingType.TIME:
|
||||
jsonPath(keyFromBody, byTime())
|
||||
break
|
||||
case MatchingType.TIMESTAMP:
|
||||
jsonPath(keyFromBody, byTimestamp())
|
||||
break
|
||||
case MatchingType.REGEX:
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
break
|
||||
}
|
||||
} else if (value.containsKey(REGEX_KEY)) {
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(requestResponseInteraction.response.status)
|
||||
if (requestResponseInteraction.response.body.state == OptionalBody.State.PRESENT) {
|
||||
def parsedBody = BasePact.parseBody(requestResponseInteraction.response)
|
||||
if (parsedBody instanceof Map) {
|
||||
body(parsedBody as Map)
|
||||
} else if (parsedBody instanceof List) {
|
||||
body(parsedBody as List)
|
||||
} else {
|
||||
body(parsedBody.toString())
|
||||
}
|
||||
}
|
||||
if (requestResponseInteraction.response?.matchingRules) {
|
||||
testMatchers {
|
||||
requestResponseInteraction.response.matchingRules.each { String key, Map<String, Object> value ->
|
||||
String keyFromBody = toKeyStartingFromBody(key)
|
||||
if (value.containsKey(MATCH_KEY)) {
|
||||
MatchingType matchingType = MatchingType.valueOf((value.get(MATCH_KEY) as String).toUpperCase())
|
||||
switch (matchingType) {
|
||||
case MatchingType.EQUALITY:
|
||||
// equality is checked by default in the standard way
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
jsonPath(keyFromBody, byDate())
|
||||
break
|
||||
case MatchingType.TIME:
|
||||
jsonPath(keyFromBody, byTime())
|
||||
break
|
||||
case MatchingType.TIMESTAMP:
|
||||
jsonPath(keyFromBody, byTimestamp())
|
||||
break
|
||||
case MatchingType.REGEX:
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
break
|
||||
case MatchingType.TYPE:
|
||||
jsonPath(keyFromBody, byType() {
|
||||
if (value.containsKey(MIN_KEY)) {
|
||||
minOccurrence(value.get(MIN_KEY) as Integer)
|
||||
}
|
||||
if (value.containsKey(MAX_KEY)) {
|
||||
maxOccurrence(value.get(MAX_KEY) as Integer)
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
} else if (value.containsKey(REGEX_KEY)) {
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requestResponseInteraction.response.headers?.each { String key, String value ->
|
||||
headers {
|
||||
header(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String providerState(Interaction interaction) {
|
||||
return interaction.providerState ? " ${interaction.providerState}" : ""
|
||||
}
|
||||
|
||||
protected String toKeyStartingFromBody(String key) {
|
||||
return key.replace('$.body', '$')
|
||||
}
|
||||
|
||||
@Override
|
||||
Pact convertTo(Collection<Contract> contract) {
|
||||
Provider provider = new Provider()
|
||||
provider.name = "Provider"
|
||||
Consumer consumer = new Consumer()
|
||||
consumer.name = "Consumer"
|
||||
List<RequestResponseInteraction> interactions = contract.find { it.request }.collect { Contract dsl ->
|
||||
RequestResponseInteraction interaction = new RequestResponseInteraction()
|
||||
interaction.description = dsl.description ?: ""
|
||||
Request request = new Request().with {
|
||||
method = dsl.request.method.serverValue.toString()
|
||||
path = url(dsl)
|
||||
QueryParameters params = queryParams(dsl)
|
||||
if (params) {
|
||||
query = params.parameters.collectEntries {
|
||||
String name = it.name
|
||||
String value = it.serverValue
|
||||
return [(name) : [value]]
|
||||
}
|
||||
}
|
||||
if (dsl.request.headers) {
|
||||
headers = headers(dsl.request.headers, { DslProperty property -> property.serverValue })
|
||||
}
|
||||
if (dsl.request.body) {
|
||||
assertInputContract(dsl.request.body.serverValue)
|
||||
def json = MapConverter.getTestSideValues(dsl.request.body.serverValue)
|
||||
String jsonBody = JsonOutput.toJson(json)
|
||||
body = new OptionalBody(OptionalBody.State.PRESENT, jsonBody)
|
||||
}
|
||||
if (dsl.request.matchers && dsl.request.matchers.hasMatchers()) {
|
||||
matchingRules = matchingRules(dsl.request.matchers)
|
||||
}
|
||||
return it
|
||||
}
|
||||
Response response = new Response().with {
|
||||
status = dsl.response.status.clientValue as Integer
|
||||
if (dsl.response.headers) {
|
||||
headers = headers(dsl.response.headers, { DslProperty property -> property.clientValue })
|
||||
}
|
||||
if (dsl.response.body) {
|
||||
assertInputContract(dsl.response.body.clientValue)
|
||||
def json = MapConverter.getStubSideValues(dsl.response.body.clientValue)
|
||||
String jsonBody = JsonOutput.toJson(json)
|
||||
body = new OptionalBody(OptionalBody.State.PRESENT, jsonBody)
|
||||
}
|
||||
if (dsl.response.matchers && dsl.response.matchers.hasMatchers()) {
|
||||
matchingRules = matchingRules(dsl.response.matchers)
|
||||
}
|
||||
return it
|
||||
}
|
||||
interaction.request = request
|
||||
interaction.response = response
|
||||
return interaction
|
||||
}
|
||||
return new RequestResponsePact(provider, consumer, interactions)
|
||||
}
|
||||
|
||||
protected void assertInputContract(parsedJson) {
|
||||
boolean hasExecutionProp = false
|
||||
MapConverter.transformValues(parsedJson, {
|
||||
if (it instanceof ExecutionProperty) {
|
||||
hasExecutionProp = true
|
||||
}
|
||||
return it
|
||||
})
|
||||
if (hasExecutionProp) {
|
||||
throw new UnsupportedOperationException("We can't convert a contract that has execution property")
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, String> headers(Headers headers, Closure closure) {
|
||||
return headers.entries.collectEntries {
|
||||
String name = it.name
|
||||
String value = closure(it)
|
||||
return [(name) : value]
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, Map<String, Object>> matchingRules(BodyMatchers bodyMatchers) {
|
||||
return bodyMatchers.jsonPathMatchers().collectEntries {
|
||||
MatchingType matchingType = it.matchingType()
|
||||
String key = it.path()
|
||||
Object value = it.value()
|
||||
Integer minTypeOccurrence = it.minTypeOccurrence()
|
||||
Integer maxTypeOccurrence = it.maxTypeOccurrence()
|
||||
Map<String, Object> matchingRule = [:]
|
||||
switch (matchingType) {
|
||||
case MatchingType.EQUALITY:
|
||||
matchingRule << [(MATCH_KEY) : MatchingType.EQUALITY.toString().toLowerCase() as Object]
|
||||
break
|
||||
case MatchingType.TYPE:
|
||||
Map<String, Object> map = [(MATCH_KEY) : MatchingType.TYPE.toString().toLowerCase() as Object]
|
||||
if (minTypeOccurrence) map.put(MIN_KEY, minTypeOccurrence)
|
||||
if (maxTypeOccurrence) map.put(MAX_KEY, maxTypeOccurrence)
|
||||
matchingRule << map
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
case MatchingType.TIME:
|
||||
case MatchingType.TIMESTAMP:
|
||||
case MatchingType.REGEX:
|
||||
matchingRule << [
|
||||
(MATCH_KEY) : MatchingType.REGEX.toString().toLowerCase() as Object,
|
||||
(REGEX_KEY) : value
|
||||
]
|
||||
break
|
||||
}
|
||||
return [(key) : matchingRule]
|
||||
}
|
||||
}
|
||||
|
||||
protected String url(Contract dsl) {
|
||||
if (dsl.request.urlPath) {
|
||||
return dsl.request.urlPath.serverValue.toString()
|
||||
} else if (dsl.request.url) {
|
||||
return dsl.request.url.serverValue.toString()
|
||||
}
|
||||
throw new IllegalStateException("No url provided")
|
||||
}
|
||||
|
||||
protected QueryParameters queryParams(Contract dsl) {
|
||||
if (dsl.request.urlPath) {
|
||||
return dsl.request.urlPath.queryParameters
|
||||
} else if (dsl.request.url) {
|
||||
return dsl.request.url.queryParameters
|
||||
}
|
||||
throw new IllegalStateException("No url provided")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.cloud.contract.spec.ContractConverter=\
|
||||
org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
|
||||
@@ -0,0 +1,290 @@
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.Pact
|
||||
import au.com.dius.pact.model.PactSpecVersion
|
||||
import groovy.json.JsonOutput
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Subject
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class PactContractConverterSpec extends Specification {
|
||||
|
||||
File pactJson = new File(PactContractConverterSpec.getResource("/pact/pact.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 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 {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body(id: "123", method: "create")
|
||||
stubMatchers {
|
||||
jsonPath('$.id', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body([[
|
||||
[email: "rddtGwwWMEhnkAPEmsyE",
|
||||
id: "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
userName: "AJQrokEGPAVdOHprQpKP"]
|
||||
]])
|
||||
testMatchers {
|
||||
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]
|
||||
}
|
||||
|
||||
def "should convert from contract to pact"() {
|
||||
given:
|
||||
Collection<Contract> inputContracts = [
|
||||
Contract.make {
|
||||
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"
|
||||
)
|
||||
stubMatchers {
|
||||
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)),
|
||||
something: "foo",
|
||||
userName: "AJQrokEGPAVdOHprQpKP"]
|
||||
]])
|
||||
testMatchers {
|
||||
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][*].userName', byType())
|
||||
jsonPath('$[0][*].something', byEquality())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
String expectedJson = '''
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "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": {
|
||||
"$.id": {
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{3}"
|
||||
},
|
||||
"$.something": {
|
||||
"match": "equality"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application\\/json"
|
||||
},
|
||||
"body": [
|
||||
[
|
||||
{
|
||||
"email": "rddtGwwWMEhnkAPEmsyE",
|
||||
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
"number": 923,
|
||||
"userName": "AJQrokEGPAVdOHprQpKP"
|
||||
}
|
||||
]
|
||||
],
|
||||
"matchingRules": {
|
||||
"$[0][*].email": {
|
||||
"match": "type"
|
||||
},
|
||||
"$[0][*].id": {
|
||||
"match": "regex",
|
||||
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
},
|
||||
"$[0]": {
|
||||
"match": "type",
|
||||
"min": 1,
|
||||
"max": 5
|
||||
},
|
||||
"$[0][*].userName": {
|
||||
"match": "type"
|
||||
},
|
||||
"$[0][*].something": {
|
||||
"match": "equality"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
when:
|
||||
Pact pact = converter.convertTo(inputContracts)
|
||||
then:
|
||||
String actual = JsonOutput.toJson(pact.toMap(PactSpecVersion.V2))
|
||||
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(it.file)] }
|
||||
Map<String, String> jsonPacts = pactResources.collectEntries { [(it.filename) : it.file.text] }
|
||||
when:
|
||||
Map<String, Pact> pacts = contracts.entrySet().collectEntries { [(it.key) : converter.convertTo(it.value)] }
|
||||
then:
|
||||
pacts.entrySet().each {
|
||||
String convertedPactAsText = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V2))
|
||||
String pactFileName = it.key.replace("groovy", "json")
|
||||
println "File name [${it.key}]"
|
||||
JSONAssert.assertEquals(jsonPacts.get(pactFileName), convertedPactAsText, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// file creator
|
||||
/*
|
||||
pacts.entrySet().each {
|
||||
new File("target/${it.key.replace("groovy", "json")}").text = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V2))
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,66 @@
|
||||
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 200 // (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.*`
|
||||
*/
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"body": {
|
||||
"fraudCheckStatus": "FRAUD",
|
||||
"rejectionReason": "Amount too high"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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 200
|
||||
body(
|
||||
fraudCheckStatus: "OK",
|
||||
rejectionReason: $(consumer(null), producer(execute('assertThatRejectionReasonIsNull($it)')))
|
||||
)
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"body": {
|
||||
"fraudCheckStatus": "OK",
|
||||
"rejectionReason": null
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package contracts
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
[
|
||||
Contract.make {
|
||||
request {
|
||||
name "should count all frauds"
|
||||
method GET()
|
||||
url '/frauds'
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body([
|
||||
count: 200
|
||||
])
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
}
|
||||
},
|
||||
Contract.make {
|
||||
request {
|
||||
method GET()
|
||||
url '/drunks'
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body([
|
||||
count: 100
|
||||
])
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"some" : "json"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user