Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package au.com.dius.pact.consumer.groovy
import au.com.dius.pact.consumer.Headers
import au.com.dius.pact.consumer.MockServer
import au.com.dius.pact.consumer.PactTestExecutionContext
import au.com.dius.pact.consumer.PactVerificationResult
import au.com.dius.pact.consumer.model.MockProviderConfig
import au.com.dius.pact.core.model.Consumer
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.PactReaderKt
import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.Provider
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.generators.Generators
import au.com.dius.pact.core.model.generators.ProviderStateGenerator
import au.com.dius.pact.core.model.matchingrules.Category
import au.com.dius.pact.core.model.matchingrules.MatchingRules
import au.com.dius.pact.core.model.matchingrules.MatchingRulesImpl
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.support.expressions.DataType
import groovy.json.JsonBuilder
import groovy.transform.CompileStatic
import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.HttpMultipartMode
import org.apache.http.entity.mime.MultipartEntityBuilder
import java.util.regex.Pattern
import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest
/**
* Builder DSL for Pact tests
*/
@SuppressWarnings('PropertyName')
class PactBuilder extends BaseBuilder {
private static final String CONTENT_TYPE = 'Content-Type'
private static final String JSON = 'application/json'
private static final String BODY = 'body'
private static final String LOCALHOST = 'localhost'
public static final String HEADER = 'header'
Consumer consumer
Provider provider
Integer port = 0
String requestDescription
List requestData = []
List responseData = []
List interactions = []
List providerStates = []
boolean requestState
/**
* Defines the service consumer
* @param consumer consumer name
*/
PactBuilder serviceConsumer(String consumer) {
this.consumer = new Consumer(consumer)
this
}
/**
* Defines the provider the consumer has a pact with
* @param provider provider name
*/
PactBuilder hasPactWith(String provider) {
this.provider = new Provider(provider)
this
}
/**
* Defines the port the provider will listen on
* @param port port number
*/
@SuppressWarnings('ConfusingMethodName')
PactBuilder port(int port) {
this.port = port
this
}
/**
* Defines the provider state the provider needs to be in for the interaction
* @param providerState provider state description
*/
PactBuilder given(String providerState) {
this.providerStates << new ProviderState(providerState)
this
}
/**
* Defines the provider state the provider needs to be in for the interaction
* @param providerState provider state description
* @param params Data parameters for the provider state
*/
PactBuilder given(String providerState, Map params) {
this.providerStates << new ProviderState(providerState, params)
this
}
/**
* Defines the start of an interaction
* @param requestDescription Description of the interaction. Must be unique.
*/
PactBuilder uponReceiving(String requestDescription) {
buildInteractions()
this.requestDescription = requestDescription
requestState = true
this
}
def buildInteractions() {
int numInteractions = Math.min(requestData.size(), responseData.size())
for (int i = 0; i < numInteractions; i++) {
MatchingRules requestMatchers = requestData[i].matchers
MatchingRules responseMatchers = responseData[i].matchers
Generators requestGenerators = requestData[i].generators
Generators responseGenerators = responseData[i].generators
Map headers = setupHeaders(requestData[i].headers ?: [:], requestMatchers, requestGenerators)
Map query = setupQueryParameters(requestData[i].query ?: [:], requestMatchers, requestGenerators)
Map responseHeaders = setupHeaders(responseData[i].headers ?: [:], responseMatchers, responseGenerators)
String path = setupPath(requestData[i].path ?: '/', requestMatchers, requestGenerators)
interactions << new RequestResponseInteraction(
requestDescription,
providerStates,
new Request(requestData[i].method ?: 'get', path, query, headers,
requestData[i].containsKey(BODY) ? OptionalBody.body(requestData[i].body.bytes, contentType(headers)) :
OptionalBody.missing(),
requestMatchers, requestGenerators),
new Response(responseData[i].status ?: 200, responseHeaders,
responseData[i].containsKey(BODY) ? OptionalBody.body(responseData[i].body.bytes,
contentType(responseHeaders)) : OptionalBody.missing(),
responseMatchers, responseGenerators), null
)
}
requestData = []
responseData = []
}
au.com.dius.pact.core.model.ContentType contentType(Map headers) {
def contentTypeHeader = headers.find { it.key.toLowerCase() == 'content-type' }
if (contentTypeHeader) {
new au.com.dius.pact.core.model.ContentType(contentTypeHeader.value.first())
} else {
au.com.dius.pact.core.model.ContentType.UNKNOWN
}
}
private static Map setupHeaders(Map headers, MatchingRules matchers, Generators generators) {
headers.collectEntries { key, value ->
def header = HEADER
if (value instanceof Matcher) {
matchers.addCategory(header).addRule(key, value.matcher)
[key, [value.value]]
} else if (value instanceof Pattern) {
def matcher = new RegexpMatcher(regex: value)
matchers.addCategory(header).addRule(key, matcher.matcher)
[key, [matcher.value]]
} else if (value instanceof GeneratedValue) {
generators.addGenerator(au.com.dius.pact.core.model.generators.Category.HEADER, key,
new ProviderStateGenerator(value.expression, DataType.STRING))
[key, [value.exampleValue]]
} else {
[key, value instanceof List ? value : [value]]
}
}
}
private static String setupPath(def path, MatchingRules matchers, Generators generators) {
def category = 'path'
if (path instanceof Matcher) {
matchers.addCategory(category).addRule(path.matcher)
path.value
} else if (path instanceof Pattern) {
def matcher = new RegexpMatcher(regex: path)
matchers.addCategory(category).addRule(matcher.matcher)
matcher.value
} else if (path instanceof GeneratedValue) {
generators.addGenerator(au.com.dius.pact.core.model.generators.Category.PATH,
new ProviderStateGenerator(path.expression, DataType.STRING))
path.exampleValue
} else {
path as String
}
}
private static Map setupQueryParameters(Map query, MatchingRules matchers, Generators generators) {
query.collectEntries { key, value ->
def category = 'query'
if (value[0] instanceof Matcher) {
matchers.addCategory(category).addRule(key, value[0].matcher)
[key, [value[0].value]]
} else if (value[0] instanceof Pattern) {
def matcher = new RegexpMatcher(regex: value[0].toString())
matchers.addCategory(category).addRule(key, matcher.matcher)
[key, [matcher.value]]
} else if (value[0] instanceof GeneratedValue) {
generators.addGenerator(au.com.dius.pact.core.model.generators.Category.QUERY, key,
new ProviderStateGenerator(value[0].expression, DataType.STRING))
[key, [value[0].exampleValue]]
} else {
[key, value]
}
}
}
/**
* Defines the request attributes (body, headers, etc.)
* @param requestData Map of attributes
*/
PactBuilder withAttributes(Map requestData) {
def request = [matchers: new MatchingRulesImpl(), generators: new Generators()] + requestData
setupBody(requestData, request)
if (requestData.query instanceof String) {
request.query = PactReaderKt.queryStringToMap(requestData.query)
} else {
request.query = requestData.query?.collectEntries { k, v ->
if (v instanceof Collection) {
[k, v]
} else {
[k, [v]]
}
}
}
this.requestData << request
this
}
private setupBody(Map requestData, Map request) {
if (requestData.containsKey(BODY)) {
def body = requestData.body
if (body instanceof PactBodyBuilder) {
request.body = body.body
request.matchers.addCategory(body.matchers)
request.generators.addGenerators(body.generators)
} else if (body != null && !(body instanceof String)) {
if (requestData.prettyPrint == null && !compactMimeTypes(requestData) || requestData.prettyPrint) {
request.body = new JsonBuilder(body).toPrettyString()
} else {
request.body = new JsonBuilder(body).toString()
}
}
}
}
/**
* Defines the response attributes (body, headers, etc.) that are returned for the request
* @param responseData Map of attributes
* @return
*/
@SuppressWarnings('DuplicateMapLiteral')
PactBuilder willRespondWith(Map responseData) {
def response = [matchers: new MatchingRulesImpl(), generators: new Generators()] + responseData
setupBody(responseData, response)
this.responseData << response
requestState = false
this
}
private static boolean compactMimeTypes(Map reqResData) {
reqResData.headers && reqResData.headers[CONTENT_TYPE] in COMPACT_MIME_TYPES
}
/**
* Allows the body to be defined using a Groovy builder pattern
* @param options The following options are available:
* - mimeType Optional mimetype for the body
* - prettyPrint If the body should be pretty printed
* @param closure Body closure
*/
PactBuilder withBody(Map options = [:], Closure closure) {
def body = new PactBodyBuilder(mimetype: options.mimeType, prettyPrintBody: options.prettyPrint)
closure.delegate = body
closure.call()
setupBody(body, options)
this
}
/**
* Allows the body to be defined using a Groovy builder pattern with an array as the root
* @param options The following options are available:
* - mimeType Optional mimetype for the body
* - prettyPrint If the body should be pretty printed
* @param array body
*/
PactBuilder withBody(Map options = [:], List array) {
def body = new PactBodyBuilder(mimetype: options.mimeType, prettyPrintBody: options.prettyPrint)
body.bodyRepresentation = body.build(array)
setupBody(body, options)
this
}
/**
* Allows the body to be defined using a Groovy builder pattern with an array as the root
* @param options The following options are available:
* - mimeType Optional mimetype for the body
* - prettyPrint If the body should be pretty printed
* @param matcher body
*/
PactBuilder withBody(Map options = [:], LikeMatcher matcher) {
def body = new PactBodyBuilder(mimetype: options.mimetype, prettyPrintBody: options.prettyPrint)
body.bodyRepresentation = body.build(matcher)
setupBody(body, options)
this
}
private setupBody(PactBodyBuilder body, Map options) {
if (requestState) {
requestData.last().body = body.body
requestData.last().matchers.addCategory(body.matchers)
requestData.last().generators.addGenerators(body.generators)
requestData.last().headers = requestData.last().headers ?: [:]
if (!requestData.last().headers[CONTENT_TYPE]) {
if (options.mimeType) {
requestData.last().headers[CONTENT_TYPE] = options.mimeType
} else {
requestData.last().headers[CONTENT_TYPE] = JSON
}
}
} else {
responseData.last().body = body.body
responseData.last().matchers.addCategory(body.matchers)
responseData.last().generators.addGenerators(body.generators)
responseData.last().headers = responseData.last().headers ?: [:]
if (!responseData.last().headers[CONTENT_TYPE]) {
if (options.mimeType) {
responseData.last().headers[CONTENT_TYPE] = options.mimeType
} else {
responseData.last().headers[CONTENT_TYPE] = JSON
}
}
}
}
/**
* Executes the providers closure in the context of the interactions defined on this builder.
* @param options Optional map of options for the run
* @param closure Test to execute
* @return The result of the test run
*/
@CompileStatic
PactVerificationResult runTest(Map options = [:], Closure closure) {
buildInteractions()
def pact = new RequestResponsePact(provider, consumer, interactions)
def pactVersion = options.specificationVersion ?: PactSpecVersion.V3
MockProviderConfig config = MockProviderConfig.httpConfig(LOCALHOST, port ?: 0, pactVersion as PactSpecVersion)
def runTest = closure
if (closure.maximumNumberOfParameters < 2) {
if (closure.maximumNumberOfParameters == 1) {
runTest = { MockServer server, PactTestExecutionContext context -> closure.call(server) }
} else {
runTest = { MockServer server, PactTestExecutionContext context -> closure.call() }
}
}
runConsumerTest(pact, config, runTest)
}
/**
* Runs the test (via the runTest method), and throws an exception if it was not successful.
* @param options Optional map of options for the run
* @param closure
*/
@SuppressWarnings('InvertedIfElse')
void runTestAndVerify(Map options = [:], Closure closure) {
PactVerificationResult result = runTest(options, closure)
if (!(result instanceof PactVerificationResult.Ok)) {
if (result instanceof PactVerificationResult.Error) {
if (!(result.mockServerState instanceof PactVerificationResult.Ok)) {
throw new AssertionError('Pact Test function failed with an exception, possibly due to ' +
result.mockServerState, result.error)
} else {
throw new AssertionError('Pact Test function failed with an exception: ' + result.error.message, result.error)
}
}
throw new PactFailedException(result)
}
}
/**
* Sets up a file upload request. This will add the correct content type header to the request
* @param partName This is the name of the part in the multipart body.
* @param fileName This is the name of the file that was uploaded
* @param fileContentType This is the content type of the uploaded file
* @param data This is the actual file contents
*/
void withFileUpload(String partName, String fileName, String fileContentType, byte[] data) {
def multipart = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
.addBinaryBody(partName, data, ContentType.create(fileContentType), fileName)
.build()
def os = new ByteArrayOutputStream()
multipart.writeTo(os)
if (requestState) {
requestData.last().body = os.toString()
requestData.last().headers = requestData.last().headers ?: [:]
requestData.last().headers[CONTENT_TYPE] = multipart.contentType.value
Category category = requestData.last().matchers.addCategory(HEADER)
category.addRule(CONTENT_TYPE, new RegexMatcher(Headers.MULTIPART_HEADER_REGEX, multipart.contentType.value))
} else {
responseData.last().body = os.toString()
responseData.last().headers = responseData.last().headers ?: [:]
responseData.last().headers[CONTENT_TYPE] = multipart.contentType.value
Category category = responseData.last().matchers.addCategory(HEADER)
category.addRule(CONTENT_TYPE, new RegexMatcher(Headers.MULTIPART_HEADER_REGEX, multipart.contentType.value))
}
}
/**
* Marks a item as to be injected from the provider state
* @param expression Expression to lookup in the provider state context
* @param exampleValue Example value to use in the consumer test
* @return example value
*/
def fromProviderState(String expression, def exampleValue) {
new GeneratedValue(expression, exampleValue)
}
@Override
@SuppressWarnings('UnnecessaryOverridingMethod')
def call(@DelegatesTo(value = PactBuilder, strategy = Closure.DELEGATE_FIRST) Closure closure) {
super.build(closure)
}
@Override
@SuppressWarnings('UnnecessaryOverridingMethod')
def build(@DelegatesTo(value = PactBuilder, strategy = Closure.DELEGATE_FIRST) Closure closure) {
super.build(closure)
}
}