Groups pacts by name and parses to get consumer and provider
fixes gh-792
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
|
||||
import au.com.dius.pact.consumer.MessagePactBuilder
|
||||
import au.com.dius.pact.consumer.dsl.DslPart
|
||||
import au.com.dius.pact.model.v3.messaging.MessagePact
|
||||
@@ -27,7 +28,6 @@ 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
|
||||
*
|
||||
@@ -40,26 +40,33 @@ class MessagePactCreator {
|
||||
|
||||
private static final Closure clientValueExtractor = { DslProperty property -> property.clientValue }
|
||||
|
||||
MessagePact createFromContract(Contract contract) {
|
||||
MessagePactBuilder messagePactBuilder = MessagePactBuilder.consumer("Consumer")
|
||||
.hasPactWith("Provider")
|
||||
.given(getGiven(contract.input))
|
||||
.expectsToReceive(getOutcome(contract))
|
||||
if (contract.outputMessage) {
|
||||
OutputMessage message = contract.outputMessage
|
||||
if (message.body) {
|
||||
DslPart pactResponseBody = BodyConverter.toPactBody(message.body, clientValueExtractor)
|
||||
if (message.bodyMatchers) {
|
||||
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(message.bodyMatchers))
|
||||
MessagePact createFromContract(List<Contract> contracts) {
|
||||
if (contracts.empty) {
|
||||
return null
|
||||
}
|
||||
Names names = NamingUtil.name(contracts.get(0))
|
||||
MessagePactBuilder pactBuilder = MessagePactBuilder.consumer(names.consumer)
|
||||
.hasPactWith(names.producer)
|
||||
contracts.each { Contract contract ->
|
||||
pactBuilder = pactBuilder
|
||||
.given(getGiven(contract.input))
|
||||
.expectsToReceive(getOutcome(contract))
|
||||
if (contract.outputMessage) {
|
||||
OutputMessage message = contract.outputMessage
|
||||
if (message.body) {
|
||||
DslPart pactResponseBody = BodyConverter.toPactBody(message.body, clientValueExtractor)
|
||||
if (message.bodyMatchers) {
|
||||
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(message.bodyMatchers))
|
||||
}
|
||||
pactResponseBody.setGenerators(ValueGeneratorConverter.extract(message, { DslProperty dslProperty -> dslProperty.serverValue }))
|
||||
pactBuilder = pactBuilder.withContent(pactResponseBody)
|
||||
}
|
||||
if (message.headers) {
|
||||
pactBuilder = pactBuilder.withMetadata(getMetadata(message.headers))
|
||||
}
|
||||
pactResponseBody.setGenerators(ValueGeneratorConverter.extract(message, { DslProperty dslProperty -> dslProperty.serverValue }))
|
||||
messagePactBuilder = messagePactBuilder.withContent(pactResponseBody)
|
||||
}
|
||||
if (message.headers) {
|
||||
messagePactBuilder = messagePactBuilder.withMetadata(getMetadata(message.headers))
|
||||
}
|
||||
}
|
||||
return messagePactBuilder.toPact()
|
||||
return pactBuilder.toPact()
|
||||
}
|
||||
|
||||
private String getGiven(Input input) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @since
|
||||
*/
|
||||
@PackageScope
|
||||
@CompileStatic
|
||||
final class NamingUtil {
|
||||
|
||||
// consumer___producer___testname
|
||||
private static final String SEPARATOR = "___"
|
||||
|
||||
protected static Names name(Contract contract) {
|
||||
String contractName = contract.name
|
||||
if (!contractName || !contractName.contains(SEPARATOR)) {
|
||||
return new Names(["Consumer", "Provider" , ""] as String[])
|
||||
}
|
||||
return new Names(contractName.split(SEPARATOR))
|
||||
}
|
||||
}
|
||||
|
||||
@PackageScope
|
||||
@CompileStatic
|
||||
class Names {
|
||||
final String consumer
|
||||
final String producer
|
||||
final String test
|
||||
|
||||
Names(String[] strings) {
|
||||
this.consumer = strings[0]
|
||||
this.producer = strings[1]
|
||||
this.test = strings.length >= 2 ? strings[2] : ""
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
return this.consumer + "_" + this.producer
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import au.com.dius.pact.model.v3.messaging.MessagePact
|
||||
import groovy.transform.CompileStatic
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.ContractConverter
|
||||
|
||||
/**
|
||||
* Converter of JSON PACT file
|
||||
*
|
||||
@@ -33,11 +32,10 @@ import org.springframework.cloud.contract.spec.ContractConverter
|
||||
@CompileStatic
|
||||
class PactContractConverter implements ContractConverter<Collection<Pact>> {
|
||||
|
||||
private RequestResponseSCContractCreator requestResponseSCContractCreator = new RequestResponseSCContractCreator()
|
||||
private MessagingSCContractCreator messagingSCContractCreator = new MessagingSCContractCreator()
|
||||
|
||||
private RequestResponsePactCreator requestResponsePactCreator = new RequestResponsePactCreator()
|
||||
private MessagePactCreator messagePactCreator = new MessagePactCreator()
|
||||
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
|
||||
boolean isAccepted(File file) {
|
||||
@@ -64,13 +62,14 @@ class PactContractConverter implements ContractConverter<Collection<Pact>> {
|
||||
@Override
|
||||
Collection<Pact> convertTo(Collection<Contract> contracts) {
|
||||
List<Pact> pactContracts = new ArrayList<>()
|
||||
for (Contract contract : contracts) {
|
||||
if (contract.request) {
|
||||
pactContracts.add(requestResponsePactCreator.createFromContract(contract))
|
||||
}
|
||||
if (contract.input) {
|
||||
pactContracts.add(messagePactCreator.createFromContract(contract))
|
||||
}
|
||||
Map<String, List<Contract>> groupedContracts = contracts.groupBy { NamingUtil.name(it).toString() }
|
||||
for (List<Contract> list : groupedContracts.values()) {
|
||||
List<Contract> httpOnly = list.findAll { it.request }
|
||||
List<Contract> messagingOnly = list.findAll { it.input }
|
||||
RequestResponsePact responsePact = requestResponsePactCreator.createFromContract(httpOnly)
|
||||
if (responsePact) pactContracts.add(responsePact)
|
||||
MessagePact messagePact = messagePactCreator.createFromContract(messagingOnly)
|
||||
if (messagePact) pactContracts.add(messagePact)
|
||||
}
|
||||
return pactContracts
|
||||
}
|
||||
|
||||
@@ -42,18 +42,29 @@ import org.springframework.cloud.contract.spec.internal.Response
|
||||
@PackageScope
|
||||
class RequestResponsePactCreator {
|
||||
|
||||
RequestResponsePact createFromContract(Contract contract) {
|
||||
assertNoExecutionProperty(contract)
|
||||
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder.consumer("Consumer")
|
||||
.hasPactWith("Provider")
|
||||
PactDslRequestWithPath pactDslRequest = createPactDslRequestWithPath(contract, pactDslWithProvider)
|
||||
PactDslResponse pactDslResponse = createPactDslResponse(contract, pactDslRequest)
|
||||
RequestResponsePact createFromContract(List<Contract> contracts) {
|
||||
if (contracts.empty) {
|
||||
return null
|
||||
}
|
||||
Names names = NamingUtil.name(contracts.get(0))
|
||||
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder
|
||||
.consumer(names.consumer).hasPactWith(names.producer)
|
||||
PactDslResponse pactDslResponse = null;
|
||||
contracts.each { Contract contract ->
|
||||
assertNoExecutionProperty(contract)
|
||||
PactDslRequestWithPath pactDslRequest = pactDslResponse ?
|
||||
createPactDslRequestWithPath(contract, pactDslResponse) :
|
||||
createPactDslRequestWithPath(contract, pactDslWithProvider)
|
||||
pactDslResponse = createPactDslResponse(contract, pactDslRequest)
|
||||
}
|
||||
return pactDslResponse.toPact()
|
||||
}
|
||||
|
||||
private void assertNoExecutionProperty(Contract contract) {
|
||||
assertNoExecutionPropertyInBody(contract.request.body, { DslProperty dslProperty -> dslProperty.serverValue })
|
||||
assertNoExecutionPropertyInBody(contract.response.body, { DslProperty dslProperty -> dslProperty.clientValue })
|
||||
assertNoExecutionPropertyInBody(contract.request.body,
|
||||
{ DslProperty dslProperty -> dslProperty.serverValue })
|
||||
assertNoExecutionPropertyInBody(contract.response.body,
|
||||
{ DslProperty dslProperty -> dslProperty.clientValue })
|
||||
}
|
||||
|
||||
private void assertNoExecutionPropertyInBody(Body body, Closure dslPropertyValueExtractor) {
|
||||
@@ -76,6 +87,32 @@ class RequestResponsePactCreator {
|
||||
}
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslResponse pactDslResponse) {
|
||||
Request request = contract.request
|
||||
PactDslRequestWithPath pactDslRequest = pactDslResponse
|
||||
.uponReceiving(contract.description ?: "")
|
||||
.path(url(request))
|
||||
.method(request.method.serverValue.toString())
|
||||
String query = query(request)
|
||||
if (query) {
|
||||
pactDslRequest = pactDslRequest.encodedQuery(query)
|
||||
}
|
||||
if (request.headers) {
|
||||
request.headers.entries.each { Header header ->
|
||||
pactDslRequest = processHeader(pactDslRequest, header)
|
||||
}
|
||||
}
|
||||
if (request.body) {
|
||||
DslPart pactRequestBody = BodyConverter.toPactBody(request.body, { DslProperty property -> property.serverValue })
|
||||
if (request.bodyMatchers) {
|
||||
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.bodyMatchers))
|
||||
}
|
||||
pactRequestBody.setGenerators(ValueGeneratorConverter.extract(request.body, { DslProperty dslProperty -> dslProperty.clientValue }))
|
||||
pactDslRequest = pactDslRequest.body(pactRequestBody)
|
||||
}
|
||||
return pactDslRequest
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslWithProvider pactDslWithProvider) {
|
||||
Request request = contract.request
|
||||
PactDslRequestWithPath pactDslRequest = pactDslWithProvider
|
||||
|
||||
@@ -135,6 +135,7 @@ class PactContractConverterSpec extends Specification {
|
||||
given:
|
||||
Collection<Contract> inputContracts = [
|
||||
Contract.make {
|
||||
name("my_consumer___my_producer___testname")
|
||||
description("a retrieve Mallory request")
|
||||
request {
|
||||
method(GET())
|
||||
@@ -196,10 +197,10 @@ class PactContractConverterSpec extends Specification {
|
||||
String expectedJson = '''
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
"name": "my_producer"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
"name": "my_consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -402,6 +403,20 @@ class PactContractConverterSpec extends Specification {
|
||||
}
|
||||
}
|
||||
|
||||
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 from pact v2 to two SC contracts"() {
|
||||
given:
|
||||
Collection<Contract> expectedContracts = [
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"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-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,36 @@
|
||||
"body": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"body": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
Reference in New Issue
Block a user