au.com.dius.pact.provider.ProviderVerifier.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pact-jvm-provider Show documentation
Show all versions of pact-jvm-provider Show documentation
Pact provider
=============
sub project of https://github.com/DiUS/pact-jvm
The pact provider is responsible for verifying that an API provider adheres to a number of pacts authored by its clients
This library provides the basic tools required to automate the process, and should be usable on its own in many instances.
Framework and build tool specific bindings will be provided in separate libraries that build on top of this core functionality.
### Provider State
Before each interaction is executed, the provider under test will have the opportunity to enter a state.
Generally the state maps to a set of fixture data for mocking out services that the provider is a consumer of (they will have their own pacts)
The pact framework will instruct the test server to enter that state by sending:
POST "${config.stateChangeUrl.url}/setup" { "state" : "${interaction.stateName}" }
### An example of running provider verification with junit
This example uses Groovy, JUnit 4 and Hamcrest matchers to run the provider verification.
As the provider service is a DropWizard application, it uses the DropwizardAppRule to startup the service before running any test.
**Warning:** It only grabs the first interaction from the pact file with the consumer, where there could be many. (This could possibly be solved with a parameterized test)
```groovy
class ReadmeExamplePactJVMProviderJUnitTest {
@ClassRule
public static final TestRule startServiceRule = new DropwizardAppRule<DropwizardConfiguration>(
TestDropwizardApplication, ResourceHelpers.resourceFilePath('dropwizard/test-config.yaml'))
private static ProviderInfo serviceProvider
private static Pact<RequestResponseInteraction> testConsumerPact
private static ConsumerInfo consumer
@BeforeClass
static void setupProvider() {
serviceProvider = new ProviderInfo('Dropwizard App')
serviceProvider.setProtocol('http')
serviceProvider.setHost('localhost')
serviceProvider.setPort(8080)
serviceProvider.setPath('/')
consumer = new ConsumerInfo()
consumer.setName('test_consumer')
consumer.setPactSource(new UrlSource(
ReadmeExamplePactJVMProviderJUnitTest.getResource('/pacts/zoo_app-animal_service.json').toString()))
testConsumerPact = DefaultPactReader.INSTANCE.loadPact(consumer.getPactSource()) as Pact<RequestResponseInteraction>
}
@Test
void runConsumerPacts() {
// grab the first interaction from the pact with consumer
Interaction interaction = testConsumerPact.interactions.get(0)
// setup the verifier
ProviderVerifier verifier = setupVerifier(interaction, serviceProvider, consumer)
// setup any provider state
// setup the client and interaction to fire against the provider
ProviderClient client = new ProviderClient(serviceProvider, new HttpClientFactory())
Map<String, Object> failures = new HashMap<>()
verifier.verifyResponseFromProvider(serviceProvider, interaction, interaction.getDescription(), failures, client)
// normally assert all good, but in this example it will fail
assertThat(failures, is(not(empty())))
verifier.displayFailures(failures)
}
private ProviderVerifier setupVerifier(Interaction interaction, ProviderInfo provider, ConsumerInfo consumer) {
ProviderVerifier verifier = new ProviderVerifier()
verifier.initialiseReporters(provider)
verifier.reportVerificationForConsumer(consumer, provider, new UrlSource('http://example.example'))
if (!interaction.getProviderStates().isEmpty()) {
for (ProviderState providerState: interaction.getProviderStates()) {
verifier.reportStateForInteraction(providerState.getName(), provider, consumer, true)
}
}
verifier.reportInteractionDescription(interaction)
return verifier
}
}
```
### An example of running provider verification with spock
This example uses groovy and spock to run the provider verification.
Again the provider service is a DropWizard application, and is using the DropwizardAppRule to startup the service.
This example runs all interactions using spocks Unroll feature
```groovy
class ReadmeExamplePactJVMProviderSpockSpec extends Specification {
@ClassRule @Shared
TestRule startServiceRule = new DropwizardAppRule<DropwizardConfiguration>(TestDropwizardApplication,
ResourceHelpers.resourceFilePath('dropwizard/test-config.yaml'))
@Shared
ProviderInfo serviceProvider
ProviderVerifier verifier
def setupSpec() {
serviceProvider = new ProviderInfo('Dropwizard App')
serviceProvider.protocol = 'http'
serviceProvider.host = 'localhost'
serviceProvider.port = 8080
serviceProvider.path = '/'
serviceProvider.hasPactWith('zoo_app') { consumer ->
consumer.pactSource = new FileSource(new File(ResourceHelpers.resourceFilePath('pacts/zoo_app-animal_service.json')))
}
}
def setup() {
verifier = new ProviderVerifier()
}
def cleanup() {
// cleanup provider state
// ie. db.truncateAllTables()
}
def cleanupSpec() {
// cleanup provider
}
@Unroll
def "Provider Pact - With Consumer #consumer"() {
expect:
!verifyConsumerPact(consumer).empty
where:
consumer << serviceProvider.consumers
}
private Map verifyConsumerPact(ConsumerInfo consumer) {
Map failures = [:]
verifier.initialiseReporters(serviceProvider)
verifier.runVerificationForConsumer(failures, serviceProvider, consumer)
if (!failures.empty) {
verifier.displayFailures(failures)
}
failures
}
}
```
package au.com.dius.pact.provider
import arrow.core.Either
import au.com.dius.pact.com.github.michaelbull.result.Ok
import au.com.dius.pact.com.github.michaelbull.result.getError
import au.com.dius.pact.core.matchers.BodyTypeMismatch
import au.com.dius.pact.core.matchers.HeaderMismatch
import au.com.dius.pact.core.matchers.MetadataMismatch
import au.com.dius.pact.core.matchers.StatusMismatch
import au.com.dius.pact.core.model.BrokerUrlSource
import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.DefaultPactReader
import au.com.dius.pact.core.model.FilteredPact
import au.com.dius.pact.core.model.Interaction
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.model.PactReader
import au.com.dius.pact.core.model.PactSource
import au.com.dius.pact.core.model.ProviderState
import au.com.dius.pact.core.model.RequestResponseInteraction
import au.com.dius.pact.core.model.Response
import au.com.dius.pact.core.model.UrlPactSource
import au.com.dius.pact.core.model.UrlSource
import au.com.dius.pact.core.model.messaging.Message
import au.com.dius.pact.core.pactbroker.PactBrokerClient
import au.com.dius.pact.core.pactbroker.TestResult
import au.com.dius.pact.core.support.hasProperty
import au.com.dius.pact.core.support.property
import au.com.dius.pact.provider.reporters.AnsiConsoleReporter
import au.com.dius.pact.provider.reporters.VerifierReporter
import groovy.lang.Closure
import io.github.classgraph.ClassGraph
import mu.KLogging
import java.io.File
import java.lang.reflect.Method
import java.net.URL
import java.net.URLClassLoader
import java.util.concurrent.Callable
import java.util.function.BiConsumer
import java.util.function.Function
import java.util.function.Predicate
import java.util.function.Supplier
import kotlin.reflect.KMutableProperty1
enum class PactVerification {
REQUEST_RESPONSE, ANNOTATED_METHOD
}
/**
* Exception indicating failure to setup pact verification
*/
class PactVerifierException(
override val message: String = "PactVerifierException",
override val cause: Throwable? = null
) : RuntimeException(message, cause)
/**
* Annotation to mark a test method for provider verification
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
annotation class PactVerifyProvider(
/**
* the tested provider name.
*/
val value: String
)
data class MessageAndMetadata(val messageData: ByteArray, val metadata: Map) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MessageAndMetadata
if (!messageData.contentEquals(other.messageData)) return false
if (metadata != other.metadata) return false
return true
}
override fun hashCode(): Int {
var result = messageData.contentHashCode()
result = 31 * result + metadata.hashCode()
return result
}
}
/**
* Interface to the provider verifier
*/
interface IProviderVerifier {
/**
* List of the all reporters to report the results of the verification to
*/
var reporters: List
/**
* Callback to determine if something is a build specific task
*/
var checkBuildSpecificTask: Function
/**
* Consumer SAM to execute the build specific task
*/
var executeBuildSpecificTask: BiConsumer
/**
* Callback to determine is the project has a particular property
*/
var projectHasProperty: Function
/**
* Callback to fetch a project property
*/
var projectGetProperty: Function
/**
* Callback to return the instance for the provider method to invoke
*/
var providerMethodInstance: Function
/**
* Callback to return the project classpath to use for looking up methods
*/
var projectClasspath: Supplier>
/**
* Callback to display a pact load error
*/
var pactLoadFailureMessage: Any?
/**
* Callback to get the provider version
*/
var providerVersion: Supplier
/**
* Callback to get the provider tag
*/
var providerTag: Supplier?
/**
* Run the verification for the given provider and return an failures in a Map
*/
fun verifyProvider(provider: ProviderInfo): MutableMap
/**
* Reports the state of the interaction to all the registered reporters
*/
fun reportStateForInteraction(state: String, provider: IProviderInfo, consumer: IConsumerInfo, isSetup: Boolean)
/**
* Finalise all the reports after verification is complete
*/
fun finaliseReports()
/**
* Displays all the failures from the verification run
*/
fun displayFailures(failures: Map)
/**
* Verifies the response from the provider against the interaction
*/
fun verifyResponseFromProvider(
provider: IProviderInfo,
interaction: RequestResponseInteraction,
interactionMessage: String,
failures: MutableMap,
client: ProviderClient
): TestResult
/**
* Verifies the response from the provider against the interaction
*/
fun verifyResponseFromProvider(
provider: IProviderInfo,
interaction: RequestResponseInteraction,
interactionMessage: String,
failures: MutableMap,
client: ProviderClient,
context: Map
): TestResult
/**
* Verifies the interaction by invoking a method on a provider test class
*/
fun verifyResponseByInvokingProviderMethods(
providerInfo: IProviderInfo,
consumer: IConsumerInfo,
interaction: Interaction,
interactionMessage: String,
failures: MutableMap
): TestResult
/**
* Compares the expected and actual responses
*/
fun verifyRequestResponsePact(
expectedResponse: Response,
actualResponse: Map,
interactionMessage: String,
failures: MutableMap,
interactionId: String
): TestResult
/**
* If publishing of verification results has been disabled
*/
fun publishingResultsDisabled(): Boolean
/**
* Display info about the interaction about to be verified
*/
fun reportInteractionDescription(interaction: Interaction)
}
/**
* Verifies the providers against the defined consumers in the context of a build plugin
*/
open class ProviderVerifier @JvmOverloads constructor (
override var pactLoadFailureMessage: Any? = null,
override var checkBuildSpecificTask: Function = Function { false },
override var executeBuildSpecificTask: BiConsumer = BiConsumer { _, _ -> },
override var projectClasspath: Supplier> = Supplier { emptyList() },
override var reporters: List = listOf(AnsiConsoleReporter("console", File("/tmp/"))),
override var providerMethodInstance: Function = Function { m -> m.declaringClass.newInstance() },
override var providerVersion: Supplier = Supplier { System.getProperty(PACT_PROVIDER_VERSION) },
override var providerTag: Supplier? = Supplier { System.getProperty(PACT_PROVIDER_TAG) }
) : IProviderVerifier {
override var projectHasProperty = Function { name -> !System.getProperty(name).isNullOrEmpty() }
override var projectGetProperty = Function { name -> System.getProperty(name) }
var verificationReporter: VerificationReporter = DefaultVerificationReporter
var stateChangeHandler: StateChange = DefaultStateChange
var pactReader: PactReader = DefaultPactReader
/**
* This will return true unless the pact.verifier.publishResults property has the value of "true"
*/
override fun publishingResultsDisabled(): Boolean {
return when {
!projectHasProperty.apply(PACT_VERIFIER_PUBLISH_RESULTS) -> verificationReporter.publishingResultsDisabled()
else -> projectGetProperty.apply(PACT_VERIFIER_PUBLISH_RESULTS)?.toLowerCase() != "true"
}
}
override fun verifyResponseByInvokingProviderMethods(
providerInfo: IProviderInfo,
consumer: IConsumerInfo,
interaction: Interaction,
interactionMessage: String,
failures: MutableMap
): TestResult {
try {
val urls = projectClasspath.get()
logger.debug { "projectClasspath = $urls" }
val classGraph = ClassGraph().enableAllInfo()
if (System.getProperty("pact.verifier.classpathscan.verbose") != null) {
classGraph.verbose()
}
if (urls.isNotEmpty()) {
classGraph.overrideClassLoaders(URLClassLoader(urls.toTypedArray()))
}
val scan = ProviderUtils.packagesToScan(providerInfo, consumer)
if (scan.isNotEmpty()) {
classGraph.whitelistPackages(*scan.toTypedArray())
}
val methodsAnnotatedWith = classGraph.scan().use { scanResult ->
scanResult.getClassesWithMethodAnnotation(PactVerifyProvider::class.qualifiedName)
.flatMap { classInfo ->
logger.debug { "found class $classInfo" }
val methodInfo = classInfo.methodInfo.filter {
it.annotationInfo.any { info ->
info.name == PactVerifyProvider::class.qualifiedName &&
info.parameterValues["value"].value == interaction.description
}
}
logger.debug { "found method $methodInfo" }
methodInfo.map { it.loadClassAndGetMethod() }
}
}
logger.debug { "Found methods = $methodsAnnotatedWith" }
if (methodsAnnotatedWith.isEmpty()) {
reporters.forEach { it.errorHasNoAnnotatedMethodsFoundForInteraction(interaction) }
throw RuntimeException("No annotated methods were found for interaction " +
"'${interaction.description}'. You need to provide a method annotated with " +
"@PactVerifyProvider(\"${interaction.description}\") on the classpath that returns the message contents.")
} else {
return if (interaction is Message) {
verifyMessagePact(methodsAnnotatedWith.toHashSet(), interaction, interactionMessage, failures)
} else {
val expectedResponse = (interaction as RequestResponseInteraction).response
var result: TestResult = TestResult.Ok
methodsAnnotatedWith.forEach {
val actualResponse = invokeProviderMethod(it, null) as Map
result = result.merge(this.verifyRequestResponsePact(expectedResponse, actualResponse, interactionMessage,
failures, interaction.interactionId.orEmpty()))
}
result
}
}
} catch (e: Exception) {
failures[interactionMessage] = e
reporters.forEach { it.verificationFailed(interaction, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE)) }
return TestResult.Failed(listOf(mapOf("message" to "Request to provider method failed with an exception",
"exception" to e, "interactionId" to interaction.interactionId)),
"Request to provider method failed with an exception")
}
}
fun displayBodyResult(
failures: MutableMap,
comparison: Either,
comparisonDescription: String,
interactionId: String
): TestResult {
return if (comparison is Either.Right && comparison.b.mismatches.isEmpty()) {
reporters.forEach { it.bodyComparisonOk() }
TestResult.Ok
} else {
reporters.forEach { it.bodyComparisonFailed(comparison) }
when (comparison) {
is Either.Left -> {
failures["$comparisonDescription has a matching body"] = comparison.a.description()
TestResult.Failed(listOf(comparison.a.toMap() + mapOf("interactionId" to interactionId, "type" to "body")),
"Body had differences")
}
is Either.Right -> {
failures["$comparisonDescription has a matching body"] = comparison.b
TestResult.Failed(listOf(comparison.b.mismatches + mapOf("interactionId" to interactionId, "type" to "body")),
"Body had differences")
}
}
}
}
fun verifyMessagePact(
methods: Set,
message: Message,
interactionMessage: String,
failures: MutableMap
): TestResult {
var result: TestResult = TestResult.Ok
methods.forEach { method ->
reporters.forEach { it.generatesAMessageWhich() }
val messageResult = invokeProviderMethod(method, providerMethodInstance.apply(method))
val actualMessage: ByteArray
var messageMetadata: Map? = null
var contentType = ContentType.JSON
when (messageResult) {
is MessageAndMetadata -> {
messageMetadata = messageResult.metadata
contentType = ContentType(Message.getContentType(messageResult.metadata))
actualMessage = messageResult.messageData
}
is Pair<*, *> -> {
messageMetadata = messageResult.second as Map
contentType = ContentType(Message.getContentType(messageMetadata))
actualMessage = messageResult.first.toString().toByteArray(contentType.asCharset())
}
is org.apache.commons.lang3.tuple.Pair<*, *> -> {
messageMetadata = messageResult.right as Map
contentType = ContentType(Message.getContentType(messageMetadata))
actualMessage = messageResult.left.toString().toByteArray(contentType.asCharset())
}
else -> {
actualMessage = messageResult.toString().toByteArray()
}
}
val comparison = ResponseComparison.compareMessage(message,
OptionalBody.body(actualMessage, contentType), messageMetadata)
val s = " generates a message which"
result = result.merge(displayBodyResult(failures, comparison.bodyMismatches,
interactionMessage + s, message.interactionId.orEmpty()))
.merge(displayMetadataResult(messageMetadata ?: emptyMap(), failures,
comparison.metadataMismatches, interactionMessage + s, message.interactionId.orEmpty()))
}
return result
}
private fun displayMetadataResult(
expectedMetadata: Map,
failures: MutableMap,
comparison: Map>,
comparisonDescription: String,
interactionId: String
): TestResult {
return if (comparison.isEmpty()) {
reporters.forEach { it.metadataComparisonOk() }
TestResult.Ok
} else {
reporters.forEach { it.includesMetadata() }
var result: TestResult = TestResult.Failed(emptyList(), "Metadata had differences")
comparison.forEach { (key, metadataComparison) ->
val expectedValue = expectedMetadata[key]
if (metadataComparison.isEmpty()) {
reporters.forEach { it.metadataComparisonOk(key, expectedValue) }
} else {
reporters.forEach { it.metadataComparisonFailed(key, expectedValue, metadataComparison) }
failures["$comparisonDescription includes metadata \"$key\" with value \"$expectedValue\""] =
metadataComparison
result = result.merge(TestResult.Failed(listOf(mapOf(key to metadataComparison,
"interactionId" to interactionId, "type" to "metadata"))))
}
}
result
}
}
override fun displayFailures(failures: Map) {
reporters.forEach { it.displayFailures(failures) }
}
override fun finaliseReports() {
reporters.forEach { it.finaliseReport() }
}
@JvmOverloads
fun verifyInteraction(
provider: IProviderInfo,
consumer: IConsumerInfo,
failures: MutableMap,
interaction: Interaction,
providerClient: ProviderClient = ProviderClient(provider, HttpClientFactory())
): TestResult {
var interactionMessage = "Verifying a pact between ${consumer.name} and ${provider.name}" +
" - ${interaction.description} "
val stateChangeResult = stateChangeHandler.executeStateChange(this, provider, consumer,
interaction, interactionMessage, failures, providerClient)
if (stateChangeResult.stateChangeResult is Ok) {
interactionMessage = stateChangeResult.message
reportInteractionDescription(interaction)
val context = mapOf(
"providerState" to stateChangeResult.stateChangeResult.value,
"interaction" to interaction
)
val result = if (ProviderUtils.verificationType(provider, consumer) ==
PactVerification.REQUEST_RESPONSE) {
logger.debug { "Verifying via request/response" }
verifyResponseFromProvider(provider, interaction as RequestResponseInteraction, interactionMessage, failures,
providerClient, context)
} else {
logger.debug { "Verifying via annotated test method" }
verifyResponseByInvokingProviderMethods(provider, consumer, interaction, interactionMessage, failures)
}
if (provider.stateChangeTeardown) {
stateChangeHandler.executeStateChangeTeardown(this, interaction, provider, consumer, providerClient)
}
return result
} else {
return TestResult.Failed(listOf(mapOf("message" to "State change request failed",
"exception" to stateChangeResult.stateChangeResult.getError(),
"interactionId" to interaction.interactionId)), "State change request failed")
}
}
override fun reportInteractionDescription(interaction: Interaction) {
reporters.forEach { it.interactionDescription(interaction) }
}
override fun verifyRequestResponsePact(
expectedResponse: Response,
actualResponse: Map,
interactionMessage: String,
failures: MutableMap,
interactionId: String
): TestResult {
val comparison = ResponseComparison.compareResponse(expectedResponse, actualResponse,
actualResponse["statusCode"] as Int, actualResponse["headers"] as Map>,
actualResponse["data"] as String?)
reporters.forEach { it.returnsAResponseWhich() }
val s = " returns a response which"
return displayStatusResult(failures, expectedResponse.status, comparison.statusMismatch,
interactionMessage + s, interactionId)
.merge(displayHeadersResult(failures, expectedResponse.headers, comparison.headerMismatches,
interactionMessage + s, interactionId))
.merge(displayBodyResult(failures, comparison.bodyMismatches,
interactionMessage + s, interactionId))
}
fun displayStatusResult(
failures: MutableMap,
status: Int,
mismatch: StatusMismatch?,
comparisonDescription: String,
interactionId: String
): TestResult {
return if (mismatch == null) {
reporters.forEach { it.statusComparisonOk(status) }
TestResult.Ok
} else {
reporters.forEach { it.statusComparisonFailed(status, mismatch.description()) }
failures["$comparisonDescription has statusResult code $status"] = mismatch.description()
TestResult.Failed(listOf(mismatch.toMap() + mapOf("interactionId" to interactionId,
"type" to "status")), "Response status did not match")
}
}
fun displayHeadersResult(
failures: MutableMap,
expected: Map>,
headers: Map>,
comparisonDescription: String,
interactionId: String
): TestResult {
return if (headers.isEmpty()) {
TestResult.Ok
} else {
reporters.forEach { it.includesHeaders() }
var result: TestResult = TestResult.Ok
headers.forEach { (key, headerComparison) ->
val expectedHeaderValue = expected[key]
if (headerComparison.isEmpty()) {
reporters.forEach { it.headerComparisonOk(key, expectedHeaderValue!!) }
} else {
reporters.forEach { it.headerComparisonFailed(key, expectedHeaderValue!!, headerComparison) }
failures["$comparisonDescription includes headers \"$key\" with value \"$expectedHeaderValue\""] =
headerComparison.joinToString(", ") { it.description() }
result = result.merge(TestResult.Failed(headerComparison.map { it.toMap() },
"Headers had differences"))
}
}
result
}
}
override fun verifyResponseFromProvider(
provider: IProviderInfo,
interaction: RequestResponseInteraction,
interactionMessage: String,
failures: MutableMap,
client: ProviderClient
) = verifyResponseFromProvider(provider, interaction, interactionMessage, failures, client, mapOf())
override fun verifyResponseFromProvider(
provider: IProviderInfo,
interaction: RequestResponseInteraction,
interactionMessage: String,
failures: MutableMap,
client: ProviderClient,
context: Map
): TestResult {
return try {
val expectedResponse = interaction.response.generatedResponse(context)
val actualResponse = client.makeRequest(interaction.request.generatedRequest(context))
verifyRequestResponsePact(expectedResponse, actualResponse, interactionMessage, failures,
interaction.interactionId.orEmpty())
} catch (e: Exception) {
failures[interactionMessage] = e
reporters.forEach {
it.requestFailed(provider, interaction, interactionMessage, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE))
}
TestResult.Failed(listOf(mapOf("message" to "Request to provider failed with an exception",
"exception" to e, "interactionId" to interaction.interactionId)),
"Request to provider method failed with an exception")
}
}
override fun verifyProvider(provider: ProviderInfo): MutableMap {
val failures = mutableMapOf()
initialiseReporters(provider)
val consumers = provider.consumers.filter(::filterConsumers)
if (consumers.isEmpty()) {
reporters.forEach { it.warnProviderHasNoConsumers(provider) }
}
consumers.forEach {
runVerificationForConsumer(failures, provider, it)
}
return failures
}
fun initialiseReporters(provider: ProviderInfo) {
reporters.forEach {
if (it.hasProperty("displayFullDiff")) {
(it.property("displayFullDiff") as KMutableProperty1)
.set(it, projectHasProperty.apply(PACT_SHOW_FULLDIFF))
}
it.initialise(provider)
}
}
@JvmOverloads
fun runVerificationForConsumer(
failures: MutableMap,
provider: IProviderInfo,
consumer: IConsumerInfo,
client: PactBrokerClient? = null
) {
val pact = FilteredPact(loadPactFileForConsumer(consumer),
Predicate { filterInteractions(it) })
reportVerificationForConsumer(consumer, provider, pact.source)
if (pact.interactions.isEmpty()) {
reporters.forEach { it.warnPactFileHasNoInteractions(pact as Pact) }
} else {
val result = pact.interactions.map {
verifyInteraction(provider, consumer, failures, it)
}.reduce { acc, result -> acc.merge(result) }
when {
pact.isFiltered() -> logger.warn {
"Skipping publishing of verification results as the interactions have been filtered"
}
publishingResultsDisabled() -> logger.warn {
"Skipping publishing of verification results as it has been disabled " +
"($PACT_VERIFIER_PUBLISH_RESULTS is not 'true')"
}
else -> verificationReporter.reportResults(pact, result, providerVersion.get() ?: "0.0.0", client,
providerTag?.get())
}
}
}
fun reportVerificationForConsumer(consumer: IConsumerInfo, provider: IProviderInfo, pactSource: PactSource?) {
when (pactSource) {
is BrokerUrlSource -> reporters.forEach {
it.reportVerificationForConsumer(consumer, provider, pactSource.tag)
val notices = consumer.notices.filter { it.`when` == "before_verification" }
if (notices.isNotEmpty()) {
it.reportVerificationNoticesForConsumer(consumer, provider, notices)
}
it.verifyConsumerFromUrl(pactSource, consumer)
}
is UrlPactSource -> reporters.forEach {
it.reportVerificationForConsumer(consumer, provider, null)
it.verifyConsumerFromUrl(pactSource, consumer)
}
else -> reporters.forEach {
it.reportVerificationForConsumer(consumer, provider, null)
if (pactSource != null) {
it.verifyConsumerFromFile(pactSource, consumer)
}
}
}
}
fun loadPactFileForConsumer(consumer: IConsumerInfo): Pact {
var pactSource = consumer.pactSource
if (pactSource is Callable<*>) {
pactSource = pactSource.call()
}
if (projectHasProperty.apply(PACT_FILTER_PACTURL)) {
val pactUrl = projectGetProperty.apply(PACT_FILTER_PACTURL)!!
pactSource = if (pactSource is BrokerUrlSource) {
pactSource.copy(url = pactUrl)
} else {
UrlSource(projectGetProperty.apply(PACT_FILTER_PACTURL)!!)
}
pactSource.encodePath = false
}
return if (pactSource is UrlPactSource) {
val options = mutableMapOf()
if (consumer.pactFileAuthentication.isNotEmpty()) {
options["authentication"] = consumer.pactFileAuthentication
}
pactReader.loadPact(pactSource, options)
} else {
try {
pactReader.loadPact(pactSource!!)
} catch (e: Exception) {
logger.error(e) { "Failed to load pact file" }
val message = generateLoadFailureMessage(consumer)
reporters.forEach { it.pactLoadFailureForConsumer(consumer, message) }
throw RuntimeException(message)
}
}
}
private fun generateLoadFailureMessage(consumer: IConsumerInfo): String {
return when (val callback = pactLoadFailureMessage) {
is Closure<*> -> callback.call(consumer).toString()
is Function<*, *> -> (callback as Function).apply(consumer).toString()
is scala.Function1<*, *> -> (callback as scala.Function1).apply(consumer).toString()
else -> callback as String
}
}
fun filterConsumers(consumer: IConsumerInfo): Boolean {
return !projectHasProperty.apply(PACT_FILTER_CONSUMERS) ||
consumer.name in projectGetProperty.apply(PACT_FILTER_CONSUMERS).toString().split(',').map { it.trim() }
}
fun filterInteractions(interaction: Interaction): Boolean {
return if (projectHasProperty.apply(PACT_FILTER_DESCRIPTION) &&
projectHasProperty.apply(PACT_FILTER_PROVIDERSTATE)) {
matchDescription(interaction) && matchState(interaction)
} else if (projectHasProperty.apply(PACT_FILTER_DESCRIPTION)) {
matchDescription(interaction)
} else if (projectHasProperty.apply(PACT_FILTER_PROVIDERSTATE)) {
matchState(interaction)
} else {
true
}
}
private fun matchState(interaction: Interaction): Boolean {
return if (interaction.providerStates.isNotEmpty()) {
interaction.providerStates.any {
projectGetProperty.apply(PACT_FILTER_PROVIDERSTATE)?.toRegex()?.matches(it.name.toString()) ?: true }
} else {
projectGetProperty.apply(PACT_FILTER_PROVIDERSTATE).isNullOrEmpty()
}
}
private fun matchDescription(interaction: Interaction): Boolean {
return projectGetProperty.apply(PACT_FILTER_DESCRIPTION)?.toRegex()?.matches(interaction.description) ?: true
}
override fun reportStateForInteraction(
state: String,
provider: IProviderInfo,
consumer: IConsumerInfo,
isSetup: Boolean
) {
reporters.forEach { it.stateForInteraction(state, provider, consumer, isSetup) }
}
companion object : KLogging() {
const val PACT_VERIFIER_PUBLISH_RESULTS = "pact.verifier.publishResults"
const val PACT_FILTER_CONSUMERS = "pact.filter.consumers"
const val PACT_FILTER_DESCRIPTION = "pact.filter.description"
const val PACT_FILTER_PROVIDERSTATE = "pact.filter.providerState"
const val PACT_FILTER_PACTURL = "pact.filter.pacturl"
const val PACT_SHOW_STACKTRACE = "pact.showStacktrace"
const val PACT_SHOW_FULLDIFF = "pact.showFullDiff"
const val PACT_PROVIDER_VERSION = "pact.provider.version"
const val PACT_PROVIDER_TAG = "pact.provider.tag"
const val PACT_PROVIDER_VERSION_TRIM_SNAPSHOT = "pact.provider.version.trimSnapshot"
fun invokeProviderMethod(m: Method, instance: Any?): Any? {
try {
return m.invoke(instance)
} catch (e: Throwable) {
throw RuntimeException("Failed to invoke provider method '${m.name}'", e)
}
}
}
}