From 3670bbd43366f131c3c74f60d5537b2048f0509c Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Tue, 20 Feb 2024 13:57:56 +1100 Subject: [PATCH] feat: Add interaction description to the verification payload sent to the Pact Broker --- .../pact/core/pactbroker/PactBrokerClient.kt | 11 ++- .../pactbroker/PactBrokerClientSpec.groovy | 77 +++++++++++++++---- .../pact/provider/junit/InteractionRunner.kt | 3 +- .../junit5/PactVerificationContext.kt | 3 +- .../junit5/PactVerificationExtension.kt | 3 +- .../PactVerificationStateChangeExtension.kt | 6 +- .../provider/spring/MvcProviderVerifier.kt | 3 +- .../spring/WebFluxProviderVerifier.kt | 6 +- .../spring/target/WebFluxTargetSpec.groovy | 6 +- .../dius/pact/provider/ProviderVerifier.kt | 77 +++++++++++++------ .../pact/provider/VerificationReporter.kt | 6 +- .../dius/pact/provider/VerificationResult.kt | 77 ++++++++++++++----- .../DefaultVerificationReporterSpec.groovy | 10 +-- .../provider/TestResultAccumulatorSpec.groovy | 2 +- .../provider/VerificationResultSpec.groovy | 8 +- 15 files changed, 211 insertions(+), 87 deletions(-) diff --git a/core/pactbroker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt b/core/pactbroker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt index 0964e29565..a44cebf4f6 100644 --- a/core/pactbroker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt +++ b/core/pactbroker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt @@ -869,12 +869,21 @@ open class PactBrokerClient( jsonObject("interactionId" to mismatches.key, "success" to true) } else { val json = jsonObject( - "interactionId" to mismatches.key, "success" to false, + "interactionId" to mismatches.key, + "success" to false, "mismatches" to jsonArray(values) ) if (exceptions != null) { json["exceptions"] = exceptions } + val interactionDescription = mismatches.value + .firstOrNull { it["interactionDescription"]?.toString().isNotEmpty() } + ?.get("interactionDescription") + ?.toString() + if (interactionDescription.isNotEmpty()) { + json["interactionDescription"] = interactionDescription + } + json } interactionJson diff --git a/core/pactbroker/src/test/groovy/au/com/dius/pact/core/pactbroker/PactBrokerClientSpec.groovy b/core/pactbroker/src/test/groovy/au/com/dius/pact/core/pactbroker/PactBrokerClientSpec.groovy index 5f2206a669..1f304a20c2 100644 --- a/core/pactbroker/src/test/groovy/au/com/dius/pact/core/pactbroker/PactBrokerClientSpec.groovy +++ b/core/pactbroker/src/test/groovy/au/com/dius/pact/core/pactbroker/PactBrokerClientSpec.groovy @@ -5,6 +5,7 @@ import au.com.dius.pact.core.support.Json import au.com.dius.pact.core.support.Result import au.com.dius.pact.core.support.json.JsonParser import au.com.dius.pact.core.support.json.JsonValue +import groovy.json.JsonOutput import kotlin.Pair import kotlin.collections.MapsKt import spock.lang.Issue @@ -318,6 +319,65 @@ class PactBrokerClientSpec extends Specification { 'the links have different case' | ['pb:Publish-Verification-Results': [HREF: 'URL']] | Result.Ok.simpleName } + def 'publishing verification results with an exception should support any type of exception'() { + given: + def halClient = Mock(IHalClient) + PactBrokerClient client = Spy(PactBrokerClient, constructorArgs: ['baseUrl']) { + newHalClient() >> halClient + } + def uploadResult = new Result.Ok(true) + halClient.postJson(_, _) >> uploadResult + def result = new TestResult.Failed([ + [exception: new AssertionError('boom')] + ], 'Failed') + def doc = ['pb:publish-verification-results': [href: '']] + + expect: + client.publishVerificationResults(doc, result, '0', null) == uploadResult + } + + def 'publishing verification results includes the interaction description if it is set'() { + given: + def halClient = Mock(IHalClient) + PactBrokerClient client = Spy(PactBrokerClient, constructorArgs: ['baseUrl']) { + newHalClient() >> halClient + } + def uploadResult = new Result.Ok(true) + def result = new TestResult.Failed([ + [ + exception: new AssertionError('boom'), + interactionDescription: 'interaction description' + ] + ], 'Failed') + def doc = ['pb:publish-verification-results': [href: '']] + def expectedJson = JsonOutput.toJson([ + providerApplicationVersion: '0', + success: false, + testResults: [ + [ + exceptions: [[exceptionClass: 'java.lang.AssertionError', message: 'boom']], + interactionDescription: 'interaction description', + interactionId: null, + mismatches: [], + success: false + ] + ], + verifiedBy: [ + implementation: 'Pact-JVM', + version: '' + ] + ]) + def actualJson + + when: + def publishResult = client.publishVerificationResults(doc, result, '0', null) + + then: + 1 * halClient.postJson(_, _) >> { args -> actualJson = args[1]; uploadResult } + publishResult == uploadResult + actualJson == expectedJson + } + def 'when fetching a pact, return the results as a Map'() { given: def halClient = Mock(IHalClient) @@ -345,23 +405,6 @@ class PactBrokerClientSpec extends Specification { result.pactFile == Json.INSTANCE.toJson([a: 'a', b: 100, _links: [:], c: [true, 10.2, 'test']]) } - def 'publishing verification results with an exception should support any type of exception'() { - given: - def halClient = Mock(IHalClient) - PactBrokerClient client = Spy(PactBrokerClient, constructorArgs: ['baseUrl']) { - newHalClient() >> halClient - } - def uploadResult = new Result.Ok(true) - halClient.postJson(_, _) >> uploadResult - def result = new TestResult.Failed([ - [exception: new AssertionError('boom')] - ], 'Failed') - def doc = ['pb:publish-verification-results': [href: '']] - - expect: - client.publishVerificationResults(doc, result, '0', null) == uploadResult - } - @SuppressWarnings('LineLength') def 'fetching pacts with selectors uses the provider-pacts-for-verification link and returns a list of results'() { given: diff --git a/provider/junit/src/main/kotlin/au/com/dius/pact/provider/junit/InteractionRunner.kt b/provider/junit/src/main/kotlin/au/com/dius/pact/provider/junit/InteractionRunner.kt index dceea63641..2056387acb 100644 --- a/provider/junit/src/main/kotlin/au/com/dius/pact/provider/junit/InteractionRunner.kt +++ b/provider/junit/src/main/kotlin/au/com/dius/pact/provider/junit/InteractionRunner.kt @@ -186,7 +186,8 @@ open class InteractionRunner( } catch (e: Throwable) { testResult = VerificationResult.Failed("Request to provider failed with an exception", description.displayName, mapOf(interaction.interactionId.orEmpty() to - listOf(VerificationFailureType.ExceptionFailure("Request to provider failed with an exception", e))), + listOf(VerificationFailureType.ExceptionFailure("Request to provider failed with an exception", + e, interaction))), pending) } finally { val updateTestResult = testResultAccumulator.updateTestResult(if (pact is FilteredPact) pact.pact else pact, interaction, diff --git a/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationContext.kt b/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationContext.kt index 4ce8c3be30..7db310fbf3 100644 --- a/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationContext.kt +++ b/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationContext.kt @@ -125,7 +125,8 @@ data class PactVerificationContext @JvmOverloads constructor( "Request to provider failed with an exception", interactionMessage, mapOf( interaction.interactionId.orEmpty() to - listOf(VerificationFailureType.ExceptionFailure("Request to provider failed with an exception", e)) + listOf(VerificationFailureType.ExceptionFailure("Request to provider failed with an exception", + e, interaction)) ), consumer.pending ) diff --git a/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationExtension.kt b/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationExtension.kt index c7efb94d2d..b8aa6da046 100644 --- a/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationExtension.kt +++ b/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationExtension.kt @@ -225,7 +225,8 @@ open class PactVerificationExtension( val failure = VerificationResult.Failed("Test method has failed with an exception: ${e.message}", failures = mapOf( interaction.interactionId.orEmpty() to - listOf(VerificationFailureType.ExceptionFailure("Test method has failed with an exception", e)) + listOf(VerificationFailureType.ExceptionFailure("Test method has failed with an exception", + e, interaction)) ) ) testResultAccumulator.updateTestResult( diff --git a/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationStateChangeExtension.kt b/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationStateChangeExtension.kt index 0a7e923782..6a657e8e1f 100644 --- a/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationStateChangeExtension.kt +++ b/provider/junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactVerificationStateChangeExtension.kt @@ -45,7 +45,8 @@ class PactVerificationStateChangeExtension( testContext.testExecutionResult.add(VerificationResult.Failed( description = "Provider state change callback failed", failures = mapOf(interaction.interactionId.orEmpty() to - listOf(VerificationFailureType.StateChangeFailure("Provider state change callback failed", error))), + listOf(VerificationFailureType.StateChangeFailure("Provider state change callback failed", error, + testContext.interaction))), pending = pending )) if (!pending) { @@ -68,7 +69,8 @@ class PactVerificationStateChangeExtension( testContext.testExecutionResult.add(VerificationResult.Failed( description = "Provider state change teardown callback failed", failures = mapOf(interaction.interactionId.orEmpty() to listOf( - VerificationFailureType.StateChangeFailure("Provider state change teardown callback failed", error))), + VerificationFailureType.StateChangeFailure("Provider state change teardown callback failed", error, + testContext.interaction))), pending = pending )) if (!pending) { diff --git a/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/MvcProviderVerifier.kt b/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/MvcProviderVerifier.kt index 189860cbe8..da36f97a5f 100644 --- a/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/MvcProviderVerifier.kt +++ b/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/MvcProviderVerifier.kt @@ -69,7 +69,8 @@ open class MvcProviderVerifier(private val debugRequestResponse: Boolean = false } return VerificationResult.Failed("Request to provider method failed with an exception", interactionMessage, mapOf(interaction.interactionId.orEmpty() to listOf( - VerificationFailureType.ExceptionFailure("Request to provider method failed with an exception", e))), + VerificationFailureType.ExceptionFailure("Request to provider method failed with an exception", + e, interaction))), pending) } } diff --git a/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/WebFluxProviderVerifier.kt b/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/WebFluxProviderVerifier.kt index 162d68abc8..ae193e9991 100644 --- a/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/WebFluxProviderVerifier.kt +++ b/provider/spring/src/main/kotlin/au/com/dius/pact/provider/spring/WebFluxProviderVerifier.kt @@ -13,6 +13,7 @@ import au.com.dius.pact.provider.VerificationResult import groovy.lang.Binding import groovy.lang.Closure import groovy.lang.GroovyShell +import io.github.oshai.kotlinlogging.KotlinLogging import org.apache.commons.lang3.StringUtils import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod @@ -29,6 +30,8 @@ import javax.mail.internet.ContentDisposition import javax.mail.internet.MimeMultipart import javax.mail.util.ByteArrayDataSource +val logger = KotlinLogging.logger {} + class WebFluxProviderVerifier : ProviderVerifier() { fun verifyResponseFromProvider( @@ -61,7 +64,8 @@ class WebFluxProviderVerifier : ProviderVerifier() { } return VerificationResult.Failed("Request to provider method failed with an exception", interactionMessage, mapOf(interaction.interactionId.orEmpty() to listOf( - VerificationFailureType.ExceptionFailure("Request to provider method failed with an exception", e))), + VerificationFailureType.ExceptionFailure("Request to provider method failed with an exception", + e, interaction))), pending) } } diff --git a/provider/spring/src/test/groovy/au/com/dius/pact/provider/spring/target/WebFluxTargetSpec.groovy b/provider/spring/src/test/groovy/au/com/dius/pact/provider/spring/target/WebFluxTargetSpec.groovy index d972aa0b9c..d19ba47802 100644 --- a/provider/spring/src/test/groovy/au/com/dius/pact/provider/spring/target/WebFluxTargetSpec.groovy +++ b/provider/spring/src/test/groovy/au/com/dius/pact/provider/spring/target/WebFluxTargetSpec.groovy @@ -58,7 +58,7 @@ class WebFluxTargetSpec extends Specification { def 'execute the test against router function'() { given: - def target = new WebFluxTarget() + WebFluxTarget target = new WebFluxTarget() target.setTestClass(new TestClass(WebFluxTargetSpec), this) def interaction = new RequestResponseInteraction('Test Interaction') def handler = Spy(TestHandler) @@ -73,7 +73,7 @@ class WebFluxTargetSpec extends Specification { def 'execute the test against controller'() { given: - def target = new WebFluxTarget() + WebFluxTarget target = new WebFluxTarget() target.setTestClass(new TestClass(WebFluxTargetSpec), this) def interaction = new RequestResponseInteraction('Test Interaction') def controller = Spy(TestController) @@ -88,7 +88,7 @@ class WebFluxTargetSpec extends Specification { def 'invokes any request filter'() { given: - def target = new WebFluxTarget() + WebFluxTarget target = new WebFluxTarget() def testInstance = Spy(TestClassWithFilter) target.setTestClass(new TestClass(TestClassWithFilter), testInstance) def interaction = new RequestResponseInteraction('Test Interaction') diff --git a/provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt b/provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt index 49cce56602..52e139f811 100644 --- a/provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt +++ b/provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt @@ -31,7 +31,6 @@ import au.com.dius.pact.core.model.messaging.MessageInteraction import au.com.dius.pact.core.model.v4.MessageContents import au.com.dius.pact.core.pactbroker.IPactBrokerClient import au.com.dius.pact.core.support.Auth -import au.com.dius.pact.core.support.Json import au.com.dius.pact.core.support.MetricEvent import au.com.dius.pact.core.support.Metrics import au.com.dius.pact.core.support.Result @@ -40,7 +39,6 @@ import au.com.dius.pact.core.support.Result.Ok import au.com.dius.pact.core.support.expressions.SystemPropertyResolver import au.com.dius.pact.core.support.hasProperty import au.com.dius.pact.core.support.ifNullOrEmpty -import au.com.dius.pact.core.support.json.JsonValue import au.com.dius.pact.core.support.property import au.com.dius.pact.provider.reporters.AnsiConsoleReporter import au.com.dius.pact.provider.reporters.Event @@ -53,7 +51,7 @@ import io.pact.plugins.jvm.core.DefaultPluginManager import io.pact.plugins.jvm.core.InteractionVerificationDetails import io.pact.plugins.jvm.core.PluginConfiguration import io.pact.plugins.jvm.core.PluginManager -import io.github.oshai.kotlinlogging.KLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File import java.lang.reflect.Method import java.net.URL @@ -64,6 +62,8 @@ import java.util.function.Function import java.util.function.Supplier import kotlin.reflect.KMutableProperty1 +private val logger = KotlinLogging.logger {} + /** * Type of verification being preformed */ @@ -410,6 +410,8 @@ open class ProviderVerifier @JvmOverloads constructor ( var pluginManager: PluginManager = DefaultPluginManager var responseComparer: IResponseComparison = ResponseComparison.Companion + private var currentInteraction: Interaction? = null + /** * This will return true unless the pact.verifier.publishResults property has the value of "true" */ @@ -443,6 +445,7 @@ open class ProviderVerifier @JvmOverloads constructor ( pending: Boolean, pluginConfiguration: Map ): VerificationResult { + currentInteraction = interaction val interactionId = interaction.interactionId try { val classGraph = setupClassGraph(providerInfo, consumer) @@ -523,7 +526,8 @@ open class ProviderVerifier @JvmOverloads constructor ( failures[interactionMessage] = e emitEvent(Event.VerificationFailed(interaction, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE))) val errors = listOf( - VerificationFailureType.ExceptionFailure("Request to provider method failed with an exception", e) + VerificationFailureType.ExceptionFailure("Request to provider method failed with an exception", + e, interaction) ) return VerificationResult.Failed( "Request to provider method failed with an exception", interactionMessage, @@ -540,6 +544,7 @@ open class ProviderVerifier @JvmOverloads constructor ( pending: Boolean, pluginConfiguration: Map ): VerificationResult { + currentInteraction = interaction var result: VerificationResult = VerificationResult.Ok(interactionId, emptyList()) methods.forEach { method -> val messageFactory = BiFunction { @@ -567,6 +572,7 @@ open class ProviderVerifier @JvmOverloads constructor ( pending: Boolean, pluginConfiguration: Map ): VerificationResult { + currentInteraction = interaction emitEvent(Event.GeneratesAMessageWhich) val messageResult = factory.apply(interaction.description, interaction.request) val actualMessage: ByteArray @@ -600,7 +606,8 @@ open class ProviderVerifier @JvmOverloads constructor ( comparison.bodyMismatches, interactionMessage + s, interactionId.orEmpty(), - pending + pending, + currentInteraction ).merge( displayMetadataResult( messageMetadata ?: emptyMap(), @@ -608,7 +615,8 @@ open class ProviderVerifier @JvmOverloads constructor ( comparison.metadataMismatches, interactionMessage + s, interactionId.orEmpty(), - pending + pending, + currentInteraction ) ) } @@ -633,6 +641,7 @@ open class ProviderVerifier @JvmOverloads constructor ( pending: Boolean, pluginConfiguration: Map ): VerificationResult { + currentInteraction = interaction val interactionId = interaction.interactionId.orEmpty() try { val factory = responseFactory!! @@ -682,7 +691,8 @@ open class ProviderVerifier @JvmOverloads constructor ( failures[interactionMessage] = e emitEvent(Event.VerificationFailed(interaction, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE))) val errors = listOf( - VerificationFailureType.ExceptionFailure("Verification factory method failed with an exception", e) + VerificationFailureType.ExceptionFailure("Verification factory method failed with an exception", + e, interaction) ) return VerificationResult.Failed( "Verification factory method failed with an exception", interactionMessage, @@ -724,7 +734,8 @@ open class ProviderVerifier @JvmOverloads constructor ( comparison: Result, comparisonDescription: String, interactionId: String, - pending: Boolean + pending: Boolean, + interaction: Interaction? ): VerificationResult { return if (comparison is Ok && comparison.value.mismatches.isEmpty()) { emitEvent(Event.BodyComparisonOk) @@ -736,13 +747,14 @@ open class ProviderVerifier @JvmOverloads constructor ( is Err -> { failures[description] = comparison.error.description() VerificationResult.Failed("Body had differences", description, - mapOf(interactionId to listOf(VerificationFailureType.MismatchFailure(comparison.error))), pending) + mapOf(interactionId to listOf(VerificationFailureType.MismatchFailure(comparison.error, interaction))), + pending) } is Ok -> { failures[description] = comparison.value VerificationResult.Failed("Body had differences", description, mapOf(interactionId to comparison.value.mismatches.values.flatten() - .map { VerificationFailureType.MismatchFailure(it) }), pending) + .map { VerificationFailureType.MismatchFailure(it, interaction) }), pending) } } } @@ -767,6 +779,7 @@ open class ProviderVerifier @JvmOverloads constructor ( pending: Boolean, pluginConfiguration: Map ): VerificationResult { + currentInteraction = message val interactionId = message.interactionId var result: VerificationResult = VerificationResult.Ok(interactionId, emptyList()) methods.forEach { method -> @@ -808,6 +821,7 @@ open class ProviderVerifier @JvmOverloads constructor ( pending: Boolean, pluginConfiguration: Map ): VerificationResult { + currentInteraction = message emitEvent(Event.GeneratesAMessageWhich) val messageResult = messageFactory.apply(message.description) val actualMessage: ByteArray @@ -841,7 +855,8 @@ open class ProviderVerifier @JvmOverloads constructor ( comparison.bodyMismatches, interactionMessage + s, interactionId, - pending + pending, + currentInteraction ).merge( displayMetadataResult( messageMetadata ?: emptyMap(), @@ -849,7 +864,8 @@ open class ProviderVerifier @JvmOverloads constructor ( comparison.metadataMismatches, interactionMessage + s, interactionId, - pending + pending, + currentInteraction ) ) } @@ -860,7 +876,8 @@ open class ProviderVerifier @JvmOverloads constructor ( comparison: Map>, comparisonDescription: String, interactionId: String, - pending: Boolean + pending: Boolean, + interaction: Interaction? ): VerificationResult { return if (comparison.isEmpty()) { emitEvent(Event.MetadataComparisonOk()) @@ -878,7 +895,8 @@ open class ProviderVerifier @JvmOverloads constructor ( val description = "$comparisonDescription includes metadata \"$key\" with value \"$expectedValue\"" failures[description] = metadataComparison result = result.merge(VerificationResult.Failed("", description, - mapOf(interactionId to metadataComparison.map { VerificationFailureType.MismatchFailure(it) }), pending)) + mapOf(interactionId to metadataComparison.map { VerificationFailureType.MismatchFailure(it, interaction) }), + pending)) } } result @@ -906,6 +924,7 @@ open class ProviderVerifier @JvmOverloads constructor ( ): VerificationResult = verifyInteraction(provider, consumer, failures, interaction, null, null, providerClient) @JvmOverloads + @SuppressWarnings("TooGenericExceptionThrown") fun verifyInteraction( provider: IProviderInfo, consumer: IConsumerInfo, @@ -915,6 +934,7 @@ open class ProviderVerifier @JvmOverloads constructor ( transportEntry: CatalogueEntry?, providerClient: ProviderClient = ProviderClient(provider, HttpClientFactory()) ): VerificationResult { + currentInteraction = interaction Metrics.sendMetrics(MetricEvent.ProviderVerificationRan(1, verificationSource.ifNullOrEmpty { "unknown" }!!)) var interactionMessage = "Verifying a pact between ${consumer.name}" @@ -998,7 +1018,8 @@ open class ProviderVerifier @JvmOverloads constructor ( return VerificationResult.Failed("State change request failed", stateChangeResult.message, mapOf(interaction.interactionId.orEmpty() to - listOf(VerificationFailureType.StateChangeFailure("Provider state change callback failed", stateChangeResult)) + listOf(VerificationFailureType.StateChangeFailure("Provider state change callback failed", + stateChangeResult, interaction)) ), pending) } } @@ -1034,11 +1055,11 @@ open class ProviderVerifier @JvmOverloads constructor ( reporters.forEach { it.returnsAResponseWhich() } return displayStatusResult(failures, expectedResponse.status, comparison.statusMismatch, - interactionMessage, interactionId, pending) + interactionMessage, interactionId, pending, currentInteraction) .merge(displayHeadersResult(failures, expectedResponse.headers, comparison.headerMismatches, - interactionMessage, interactionId, pending)) + interactionMessage, interactionId, pending, currentInteraction)) .merge(displayBodyResult(failures, comparison.bodyMismatches, - interactionMessage, interactionId, pending)) + interactionMessage, interactionId, pending, currentInteraction)) } fun displayStatusResult( @@ -1047,7 +1068,8 @@ open class ProviderVerifier @JvmOverloads constructor ( mismatch: StatusMismatch?, comparisonDescription: String, interactionId: String, - pending: Boolean + pending: Boolean, + interaction: Interaction? ): VerificationResult { return if (mismatch == null) { reporters.forEach { it.statusComparisonOk(status) } @@ -1057,7 +1079,7 @@ open class ProviderVerifier @JvmOverloads constructor ( val description = "$comparisonDescription: has status code $status" failures[description] = mismatch.description() VerificationResult.Failed("Response status did not match", description, - mapOf(interactionId to listOf(VerificationFailureType.MismatchFailure(mismatch))), pending) + mapOf(interactionId to listOf(VerificationFailureType.MismatchFailure(mismatch, interaction))), pending) } } @@ -1067,7 +1089,8 @@ open class ProviderVerifier @JvmOverloads constructor ( headers: Map>, comparisonDescription: String, interactionId: String, - pending: Boolean + pending: Boolean, + interaction: Interaction? ): VerificationResult { val ok = VerificationResult.Ok(interactionId, emptyList()) return if (headers.isEmpty()) { @@ -1085,7 +1108,7 @@ open class ProviderVerifier @JvmOverloads constructor ( failures[description] = headerComparison.joinToString(", ") { it.description() } result = result.merge(VerificationResult.Failed("Headers had differences", description, mapOf(interactionId to headerComparison.map { - VerificationFailureType.MismatchFailure(it) + VerificationFailureType.MismatchFailure(it, interaction) }), pending)) } } @@ -1111,6 +1134,7 @@ open class ProviderVerifier @JvmOverloads constructor ( context: MutableMap, pending: Boolean ): VerificationResult { + currentInteraction = interaction return try { val expectedResponse = interaction.response.generatedResponse(context, GeneratorTestMode.Provider) val actualResponse = client.makeRequest(interaction.request.generatedRequest(context, GeneratorTestMode.Provider)) @@ -1131,7 +1155,8 @@ open class ProviderVerifier @JvmOverloads constructor ( } VerificationResult.Failed("Request to provider endpoint failed with an exception", interactionMessage, mapOf(interaction.interactionId.orEmpty() to - listOf(VerificationFailureType.ExceptionFailure("Request to provider endpoint failed with an exception", e))), + listOf(VerificationFailureType.ExceptionFailure("Request to provider endpoint failed with an exception", + e, interaction))), pending) } } @@ -1210,6 +1235,7 @@ open class ProviderVerifier @JvmOverloads constructor ( /** * Initialise any required plugins and plugin entries required for the verification */ + @SuppressWarnings("TooGenericExceptionThrown") fun initialisePlugins(pact: Pact) { CatalogueManager.registerCoreEntries( MatchingConfig.contentMatcherCatalogueEntries() + @@ -1267,6 +1293,7 @@ open class ProviderVerifier @JvmOverloads constructor ( request: Any?, context: Map ): VerificationResult { + currentInteraction = interaction val userConfig = context["userConfig"] as Map? ?: emptyMap() logger.debug { "Verifying interaction => $request" } return when (val result = DefaultPluginManager.verifyInteraction( @@ -1285,7 +1312,7 @@ open class ProviderVerifier @JvmOverloads constructor ( when (it) { is InteractionVerificationDetails.Error -> VerificationFailureType.InvalidInteractionFailure(it.message) is InteractionVerificationDetails.Mismatch -> VerificationFailureType.MismatchFailure( - BodyMismatch(it.expected, it.actual, it.mismatch, it.path) + BodyMismatch(it.expected, it.actual, it.mismatch, it.path), interaction ) } }), output = result.value.output @@ -1385,7 +1412,7 @@ open class ProviderVerifier @JvmOverloads constructor ( reporters.forEach { it.stateForInteraction(state, provider, consumer, isSetup) } } - companion object : KLogging() { + companion object { const val PACT_VERIFIER_PUBLISH_RESULTS = "pact.verifier.publishResults" const val PACT_VERIFIER_BUILD_URL = "pact.verifier.buildUrl" const val PACT_FILTER_CONSUMERS = "pact.filter.consumers" diff --git a/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt b/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt index bf2aaf2603..a106a5608e 100644 --- a/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt +++ b/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt @@ -114,11 +114,7 @@ object DefaultVerificationReporter : VerificationReporter, KLogging() { Result.Ok(true) } val buildUrl = System.getProperty(ProviderVerifier.PACT_VERIFIER_BUILD_URL) - val publishResult = if (buildUrl.isNullOrEmpty()) { - brokerClient.publishVerificationResults(source.attributes, result, version) - } else { - brokerClient.publishVerificationResults(source.attributes, result, version, buildUrl) - } + val publishResult = brokerClient.publishVerificationResults(source.attributes, result, version, buildUrl) if (publishResult is Result.Err) { logger.error { "Failed to publish verification results - ${publishResult.error}" } } else { diff --git a/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationResult.kt b/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationResult.kt index 17d6e1f289..c24d84b3b0 100644 --- a/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationResult.kt +++ b/provider/src/main/kotlin/au/com/dius/pact/provider/VerificationResult.kt @@ -67,7 +67,11 @@ sealed class VerificationFailureType { } } - data class ExceptionFailure(val description: String, val e: Throwable) : VerificationFailureType() { + data class ExceptionFailure( + val description: String, + val e: Throwable, + val interaction: Interaction? = null + ) : VerificationFailureType() { override fun description() = e.message ?: e.javaClass.name override fun formatForDisplay(t: TermColors): String { return if (e.message.isNotEmpty()) { @@ -81,7 +85,11 @@ sealed class VerificationFailureType { override fun getException() = e } - data class StateChangeFailure(val description: String, val result: StateChangeResult) : VerificationFailureType() { + data class StateChangeFailure( + val description: String, + val result: StateChangeResult, + val interaction: Interaction? = null, + ) : VerificationFailureType() { override fun description() = formatForDisplay(TermColors()) override fun formatForDisplay(t: TermColors): String { val e = result.stateChangeResult.errorValue() @@ -174,24 +182,55 @@ sealed class VerificationResult { if (entry.value.isNotEmpty()) { entry.value.map { failure -> val errorMap = when (failure) { - is VerificationFailureType.ExceptionFailure -> listOf( - "exception" to failure.getException(), "description" to failure.description - ) - is VerificationFailureType.StateChangeFailure -> listOf( - "exception" to failure.getException(), "description" to failure.description - ) - is VerificationFailureType.MismatchFailure -> listOf( - "attribute" to failure.mismatch.type(), - "description" to failure.mismatch.description() - ) + when (val mismatch = failure.mismatch) { - is BodyMismatch -> listOf( - "identifier" to mismatch.path, "description" to mismatch.mismatch, - "diff" to mismatch.diff + is VerificationFailureType.ExceptionFailure -> { + val list = mutableListOf( + "exception" to failure.getException(), + "description" to failure.description + ) + if (failure.interaction != null) { + list.add("interactionDescription" to failure.interaction.description) + } + list + } + is VerificationFailureType.StateChangeFailure -> { + val list = mutableListOf( + "exception" to failure.getException(), + "description" to failure.description + ) + if (failure.interaction != null) { + list.add("interactionDescription" to failure.interaction.description) + } + list + } + is VerificationFailureType.MismatchFailure -> { + val list = mutableListOf>( + "attribute" to failure.mismatch.type(), + "description" to failure.mismatch.description() ) - is HeaderMismatch -> listOf("identifier" to mismatch.headerKey, "description" to mismatch.mismatch) - is QueryMismatch -> listOf("identifier" to mismatch.queryParameter, "description" to mismatch.mismatch) - is MetadataMismatch -> listOf("identifier" to mismatch.key, "description" to mismatch.mismatch) - else -> listOf() + when (val mismatch = failure.mismatch) { + is BodyMismatch -> { + list.add("identifier" to mismatch.path) + list.add("description" to mismatch.mismatch) + list.add("diff" to mismatch.diff) + } + is HeaderMismatch -> { + list.add("identifier" to mismatch.headerKey) + list.add("description" to mismatch.mismatch) + } + is QueryMismatch -> { + list.add("identifier" to mismatch.queryParameter) + list.add("description" to mismatch.mismatch) + } + is MetadataMismatch -> { + list.add("identifier" to mismatch.key) + list.add("description" to mismatch.mismatch) + } + else -> {} + } + if (failure.interaction != null) { + list.add("interactionDescription" to failure.interaction.description) + } + list } is VerificationFailureType.PublishResultsFailure -> listOf( "description" to failure.description() diff --git a/provider/src/test/groovy/au/com/dius/pact/provider/DefaultVerificationReporterSpec.groovy b/provider/src/test/groovy/au/com/dius/pact/provider/DefaultVerificationReporterSpec.groovy index c4dbd7c1ab..5ef35cfb0b 100644 --- a/provider/src/test/groovy/au/com/dius/pact/provider/DefaultVerificationReporterSpec.groovy +++ b/provider/src/test/groovy/au/com/dius/pact/provider/DefaultVerificationReporterSpec.groovy @@ -30,7 +30,7 @@ class DefaultVerificationReporterSpec extends Specification { def result = DefaultVerificationReporter.INSTANCE.reportResults(pact, testResult, '0', brokerClient, [], null) then: - 1 * brokerClient.publishVerificationResults(links, testResult, '0') >> new Result.Ok(true) + 1 * brokerClient.publishVerificationResults(links, testResult, '0', null) >> new Result.Ok(true) result == new Result.Ok(true) } @@ -107,7 +107,7 @@ class DefaultVerificationReporterSpec extends Specification { def result = DefaultVerificationReporter.INSTANCE.reportResults(pact, testResult, '', brokerClient, [], null) then: - 1 * brokerClient.publishVerificationResults(_, testResult, _) >> new Result.Err('failed') + 1 * brokerClient.publishVerificationResults(_, testResult, _, _) >> new Result.Err('failed') result == new Result.Err(['failed']) } @@ -127,7 +127,7 @@ class DefaultVerificationReporterSpec extends Specification { then: 0 * brokerClient.publishProviderBranch(_, 'provider', _, '') 1 * brokerClient.publishProviderTags(_, 'provider', tags, '') >> new Result.Err(['failed']) - 1 * brokerClient.publishVerificationResults(_, testResult, _) >> new Result.Ok(true) + 1 * brokerClient.publishVerificationResults(_, testResult, _, _) >> new Result.Ok(true) result == new Result.Err(['failed']) } @@ -147,7 +147,7 @@ class DefaultVerificationReporterSpec extends Specification { then: 0 * brokerClient.publishProviderTags(_, 'provider', _, '') 1 * brokerClient.publishProviderBranch(_, 'provider', branch, '') >> new Result.Err('failed') - 1 * brokerClient.publishVerificationResults(_, testResult, _) >> new Result.Ok(true) + 1 * brokerClient.publishVerificationResults(_, testResult, _, _) >> new Result.Ok(true) result == new Result.Err(['failed']) } @@ -168,7 +168,7 @@ class DefaultVerificationReporterSpec extends Specification { then: 1 * brokerClient.publishProviderTags(_, 'provider', tags, '') >> new Result.Err(['tags failed']) 1 * brokerClient.publishProviderBranch(_, 'provider', branch, '') >> new Result.Err('branch failed') - 1 * brokerClient.publishVerificationResults(_, testResult, _) >> new Result.Ok(true) + 1 * brokerClient.publishVerificationResults(_, testResult, _, _) >> new Result.Ok(true) result == new Result.Err(['tags failed', 'branch failed']) } } diff --git a/provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy b/provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy index 262d5adf90..e2fb9b00c9 100644 --- a/provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy +++ b/provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy @@ -377,7 +377,7 @@ class TestResultAccumulatorSpec extends Specification { testResultAccumulator.updateTestResult(pact3, interaction2_4, [new VerificationResult.Failed('failed', 'failed', [ - interaction2_4: [new VerificationFailureType.ExceptionFailure('failed', exception)] + interaction2_4: [new VerificationFailureType.ExceptionFailure('failed', exception, null)] ], true) ], source3, mockValueResolver) testResultAccumulator.updateTestResult(pact2, interaction2_2, diff --git a/provider/src/test/groovy/au/com/dius/pact/provider/VerificationResultSpec.groovy b/provider/src/test/groovy/au/com/dius/pact/provider/VerificationResultSpec.groovy index f4cdcc99d0..56ddb8d9d3 100644 --- a/provider/src/test/groovy/au/com/dius/pact/provider/VerificationResultSpec.groovy +++ b/provider/src/test/groovy/au/com/dius/pact/provider/VerificationResultSpec.groovy @@ -11,8 +11,8 @@ import spock.lang.Unroll @SuppressWarnings('LineLength') class VerificationResultSpec extends Specification { - static error1 = new VerificationFailureType.ExceptionFailure('Boom', new RuntimeException()) - static error2 = new VerificationFailureType.ExceptionFailure('Splat', new RuntimeException()) + static error1 = new VerificationFailureType.ExceptionFailure('Boom', new RuntimeException(), null) + static error2 = new VerificationFailureType.ExceptionFailure('Splat', new RuntimeException(), null) @Unroll def 'merging results test'() { @@ -43,7 +43,7 @@ class VerificationResultSpec extends Specification { given: def description = 'Request to provider failed with an exception' def failures = [ - '1234ABCD': [new VerificationFailureType.ExceptionFailure('Request to provider method failed with an exception', new RuntimeException('Boom'))] + '1234ABCD': [new VerificationFailureType.ExceptionFailure('Request to provider method failed with an exception', new RuntimeException('Boom'), null)] ] def verification = new VerificationResult.Failed(description, '', failures, false) @@ -62,7 +62,7 @@ class VerificationResultSpec extends Specification { given: def description = 'Provider state change callback failed' def failures = [ - '1234ABCD': [new VerificationFailureType.StateChangeFailure(description, new StateChangeResult(new Result.Err(new RuntimeException('Boom'))))] + '1234ABCD': [new VerificationFailureType.StateChangeFailure(description, new StateChangeResult(new Result.Err(new RuntimeException('Boom'))), null)] ] def verification = new VerificationResult.Failed(description, '', failures, false)