diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b291278e8..85f7b43fc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ To generate the log, run `git log --pretty='* %h - %s (%an, %ad)' TAGNAME..HEAD` replacing TAGNAME and HEAD as appropriate. +# 3.6.6 - Bugfix Release + +* ad59aa4d - refactor: verification methods now return test results instead of boolean (Ronald Holshausen, Sun May 5 16:07:28 2019 +1000) +* dcd05213 - feat: update the verification results to be a test result instead of a boolean (Ronald Holshausen, Sun May 5 12:16:56 2019 +1000) +* ae17dc5b - doc: updated doco about using bearer tokens (Ronald Holshausen, Sat May 4 17:30:51 2019 +1000) +* a88e6a17 - fix: the build (Ronald Holshausen, Sat May 4 17:21:14 2019 +1000) +* b26bed53 - feat: added pact test for publishing verification results (Ronald Holshausen, Sat May 4 16:48:23 2019 +1000) +* 1610f9d3 - feat: added pact-publish module to enable pactception (Ronald Holshausen, Sat May 4 16:28:48 2019 +1000) +* ac704677 - fix: for failing build #876 (Ronald Holshausen, Fri May 3 18:21:50 2019 +1000) +* cf937f44 - fix: correct the use of matchers with query parameters #876 (Ronald Holshausen, Fri May 3 18:05:48 2019 +1000) +* 3c5c65b7 - doc: add note about using Maven isolated classpath and message verification tests #763 (Ronald Holshausen, Fri May 3 16:43:27 2019 +1000) +* 2f447e9d - test: updated JUnit 5 test with a does not exist example (Ronald Holshausen, Thu Apr 25 18:29:43 2019 +1000) +* a1b97980 - fix: correctly handle the message content type with charsets when verifying #874 (Ronald Holshausen, Thu Apr 25 17:51:53 2019 +1000) +* efe3e0fe - fix: correctly handle the message content type with charsets #874 (Ronald Holshausen, Thu Apr 25 17:17:02 2019 +1000) +* dbe9bc21 - fix: only set the message content type if it has not been specified #874 (Ronald Holshausen, Thu Apr 25 16:06:44 2019 +1000) +* 3d43a33a - chore: update clojure to latest (Ronald Holshausen, Mon Apr 22 11:16:22 2019 +1000) +* 3c8267a5 - Revert "fix: exclude clojure test from appveyor build" (Ronald Holshausen, Mon Apr 22 11:15:54 2019 +1000) +* 616ade41 - chore: update nebula.clojure plugin (Ronald Holshausen, Mon Apr 22 11:09:55 2019 +1000) +* 49bf7fc5 - Update README.md (Ronald Holshausen, Mon Apr 22 11:02:29 2019 +1000) +* 550cf6f7 - bump version to 3.6.6 (Ronald Holshausen, Sun Apr 21 15:38:11 2019 +1000) + # 3.6.5 - Bugfix Release + Matching on message metadata * c3b8e3ad - fix: exclude clojure test from appveyor build (Ronald Holshausen, Sun Apr 21 14:57:07 2019 +1000) diff --git a/consumer/pact-jvm-consumer/src/main/kotlin/au/com/dius/pact/consumer/PactVerificationResult.kt b/consumer/pact-jvm-consumer/src/main/kotlin/au/com/dius/pact/consumer/PactVerificationResult.kt index 303986eaa7..1f7eaf2b99 100644 --- a/consumer/pact-jvm-consumer/src/main/kotlin/au/com/dius/pact/consumer/PactVerificationResult.kt +++ b/consumer/pact-jvm-consumer/src/main/kotlin/au/com/dius/pact/consumer/PactVerificationResult.kt @@ -20,9 +20,7 @@ sealed class PactVerificationResult { } data class UnexpectedRequest(val request: Request) : PactVerificationResult() { - override fun getDescription(): String { - return "Unexpected Request:\n$request" - } + override fun getDescription() = "Unexpected Request:\n$request" } data class ExpectedButNotReceived(val expectedRequests: List) : PactVerificationResult() { diff --git a/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/HalClient.kt b/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/HalClient.kt index a10e76baf5..3752001c1b 100644 --- a/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/HalClient.kt +++ b/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/HalClient.kt @@ -3,6 +3,7 @@ package au.com.dius.pact.core.pactbroker import au.com.dius.pact.com.github.michaelbull.result.Err import au.com.dius.pact.com.github.michaelbull.result.Ok import au.com.dius.pact.com.github.michaelbull.result.Result +import au.com.dius.pact.core.support.isNotEmpty import au.com.dius.pact.core.pactbroker.util.HttpClientUtils.buildUrl import au.com.dius.pact.core.pactbroker.util.HttpClientUtils.isJsonResponse import com.github.salomonbrys.kotson.array @@ -367,14 +368,14 @@ open class HalClient @JvmOverloads constructor( if (resp.entity.contentType != null) { val contentType = ContentType.getOrDefault(resp.entity) if (isJsonResponse(contentType)) { - var error = "Unknown error" - if (body != null) { + var error = "" + if (body.isNotEmpty()) { val jsonBody = JsonParser().parse(body) if (jsonBody != null && jsonBody.obj.has("errors")) { if (jsonBody["errors"].isJsonArray) { - error = jsonBody["errors"].asJsonArray.joinToString(", ") { it.asString } + error = " - " + jsonBody["errors"].asJsonArray.joinToString(", ") { it.asString } } else if (jsonBody["errors"].isJsonObject) { - error = jsonBody["errors"].asJsonObject.entrySet().joinToString(", ") { + error = " - " + jsonBody["errors"].asJsonObject.entrySet().joinToString(", ") { if (it.value.isJsonArray) { "${it.key}: ${it.value.array.joinToString(", ") { it.asString }}" } else { @@ -384,7 +385,7 @@ open class HalClient @JvmOverloads constructor( } } } - return closure.apply("FAILED", "${resp.statusLine.statusCode} ${resp.statusLine.reasonPhrase} - $error") + return closure.apply("FAILED", "${resp.statusLine.statusCode} ${resp.statusLine.reasonPhrase}$error") } else { return closure.apply("FAILED", "${resp.statusLine.statusCode} ${resp.statusLine.reasonPhrase} - $body") } diff --git a/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt b/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt index a18a228217..a4b5b506d7 100644 --- a/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt +++ b/core/pact-broker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt @@ -18,6 +18,33 @@ import java.util.function.Consumer */ data class PactResponse(val pactFile: Any, val links: Map>) +sealed class TestResult { + object Ok: TestResult() { + override fun toBoolean() = true + + override fun merge(result: TestResult) = when (result) { + is Ok -> this + is Failed -> result + } + } + + data class Failed(var results: List = emptyList()): TestResult() { + override fun toBoolean() = false + + override fun merge(result: TestResult) = when (result) { + is Ok -> this + is Failed -> Failed(results + result.results) + } + } + + abstract fun toBoolean(): Boolean + abstract fun merge(result: TestResult): TestResult + + companion object { + fun fromBoolean(result: Boolean) = if (result) Ok else Failed() + } +} + /** * Client for the pact broker service */ @@ -114,20 +141,30 @@ open class PactBrokerClient(val pactBrokerUrl: String, val options: Map>, + result: Boolean, + version: String, + buildUrl: String? = null + ): Result + = publishVerificationResults(docAttributes, TestResult.fromBoolean(result), version, buildUrl) + /** * Publishes the result to the "pb:publish-verification-results" link in the document attributes. */ @JvmOverloads open fun publishVerificationResults( docAttributes: Map>, - result: Boolean, + result: TestResult, version: String, buildUrl: String? = null ): Result { val halClient = newHalClient() val publishLink = docAttributes.mapKeys { it.key.toLowerCase() } ["pb:publish-verification-results"] // ktlint-disable curly-spacing return if (publishLink != null) { - val jsonObject = jsonObject("success" to result, "providerApplicationVersion" to version) + val jsonObject = jsonObject("success" to result.toBoolean(), "providerApplicationVersion" to version) if (buildUrl != null) { jsonObject.add("buildUrl", buildUrl.toJson()) } diff --git a/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/HalClientSpec.groovy b/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/HalClientSpec.groovy index 3bd98b1b28..8c9687f627 100644 --- a/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/HalClientSpec.groovy +++ b/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/HalClientSpec.groovy @@ -264,8 +264,8 @@ class HalClientSpec extends Specification { where: description | body | firstArg | secondArg - 'body is null' | null | 'FAILED' | '400 Not OK - Unknown error' - 'body is a parsed json doc with no errors' | '{}' | 'FAILED' | '400 Not OK - Unknown error' + 'body is null' | null | 'FAILED' | '400 Not OK' + 'body is a parsed json doc with no errors' | '{}' | 'FAILED' | '400 Not OK' 'body is a parsed json doc with errors' | '{"errors":["one","two","three"]}' | 'FAILED' | '400 Not OK - one, two, three' } diff --git a/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/TestResultSpec.groovy b/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/TestResultSpec.groovy new file mode 100644 index 0000000000..dbd30ddd88 --- /dev/null +++ b/core/pact-broker/src/test/groovy/au/com/dius/pact/core/pactbroker/TestResultSpec.groovy @@ -0,0 +1,23 @@ +package au.com.dius.pact.core.pactbroker + +import spock.lang.Specification +import spock.lang.Unroll + +@SuppressWarnings('LineLength') +class TestResultSpec extends Specification { + + @Unroll + def 'merging results test'() { + expect: + result1.merge(result2) == result3 + + where: + + result1 | result2 | result3 + TestResult.Ok.INSTANCE | TestResult.Ok.INSTANCE | TestResult.Ok.INSTANCE + TestResult.Ok.INSTANCE | new TestResult.Failed(['Bang']) | new TestResult.Failed(['Bang']) + new TestResult.Failed(['Bang']) | TestResult.Ok.INSTANCE | new TestResult.Failed(['Bang']) + new TestResult.Failed(['Bang']) | new TestResult.Failed(['Boom', 'Splat']) | new TestResult.Failed(['Bang', 'Boom', 'Splat']) + } + +} diff --git a/pact-publish/README.md b/pact-publish/README.md new file mode 100644 index 0000000000..ed7d50f1cf --- /dev/null +++ b/pact-publish/README.md @@ -0,0 +1,3 @@ +# Pact Publish + +Module for generating and publishing pacts to the pact broker diff --git a/pact-publish/build.gradle b/pact-publish/build.gradle new file mode 100644 index 0000000000..8397953729 --- /dev/null +++ b/pact-publish/build.gradle @@ -0,0 +1,25 @@ +buildscript { + dependencies { + classpath "au.com.dius:pact-jvm-provider-gradle:4.0.0-beta.1" + } +} + +if (System.env.PACT_PUBLISH == 'true') { + apply plugin: 'au.com.dius.pact' +} + +dependencies { + testCompile project(":consumer:pact-jvm-consumer-groovy") +} + +if (System.env.PACT_PUBLISH == 'true') { + pact { + publish { + pactBrokerUrl = 'https://pact-foundation.pact.dius.com.au' + if (project.hasProperty('pactBrokerToken')) { + pactBrokerToken = project.pactBrokerToken + } + excludes = ['JVM Pact Broker Client-Imaginary Pact Broker'] + } + } +} diff --git a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/broker/PactBrokerClientPactSpec.groovy b/pact-publish/src/test/groovy/broker/PactBrokerClientPactSpec.groovy similarity index 65% rename from provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/broker/PactBrokerClientPactSpec.groovy rename to pact-publish/src/test/groovy/broker/PactBrokerClientPactSpec.groovy index 6fa96e30b2..51999ec4d5 100644 --- a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/broker/PactBrokerClientPactSpec.groovy +++ b/pact-publish/src/test/groovy/broker/PactBrokerClientPactSpec.groovy @@ -1,8 +1,10 @@ -package au.com.dius.pact.provider.broker +package broker +import au.com.dius.pact.com.github.michaelbull.result.Ok import au.com.dius.pact.consumer.PactVerificationResult import au.com.dius.pact.consumer.groovy.PactBuilder import au.com.dius.pact.core.pactbroker.PactBrokerClient +import au.com.dius.pact.core.pactbroker.TestResult import spock.lang.Specification @SuppressWarnings('UnnecessaryGetter') @@ -11,7 +13,7 @@ class PactBrokerClientPactSpec extends Specification { private PactBrokerClient pactBrokerClient private File pactFile private String pactContents - private PactBuilder pactBroker + private PactBuilder pactBroker, imaginaryBroker def setup() { pactBrokerClient = new PactBrokerClient('http://localhost:8080') @@ -34,6 +36,13 @@ class PactBrokerClientPactSpec extends Specification { hasPactWith 'Pact Broker' port 8080 } + + imaginaryBroker = new PactBuilder() + imaginaryBroker { + serviceConsumer 'JVM Pact Broker Client' + hasPactWith 'Imaginary Pact Broker' + port 8080 + } } def 'returns success when uploading a pact is ok'() { @@ -53,7 +62,29 @@ class PactBrokerClientPactSpec extends Specification { } then: - result instanceof PactVerificationResult.Ok + result == PactVerificationResult.Ok.INSTANCE + } + + def 'returns an error when forbidden to publish the pact'() { + given: + pactBroker { + uponReceiving('a pact publish request which will be forbidden') + withAttributes(method: 'PUT', + path: '/pacts/provider/Provider/consumer/Foo Consumer/version/10.0.0', + body: pactContents + ) + willRespondWith(status: 401, headers: [ + 'Content-Type': 'application/json' + ]) + } + + when: + def result = pactBroker.runTest { server, context -> + assert pactBrokerClient.uploadPactFile(pactFile, '10.0.0') == 'FAILED! 401 Unauthorized' + } + + then: + result == PactVerificationResult.Ok.INSTANCE } @SuppressWarnings('LineLength') @@ -87,7 +118,7 @@ class PactBrokerClientPactSpec extends Specification { } then: - result instanceof PactVerificationResult.Ok + result == PactVerificationResult.Ok.INSTANCE } @SuppressWarnings('LineLength') @@ -118,13 +149,13 @@ class PactBrokerClientPactSpec extends Specification { } then: - result instanceof PactVerificationResult.Ok + result == PactVerificationResult.Ok.INSTANCE } @SuppressWarnings('LineLength') def 'handles non-json failure responses'() { given: - pactBroker { + imaginaryBroker { given('Non-JSON response') uponReceiving('a pact publish request') withAttributes(method: 'PUT', @@ -137,12 +168,12 @@ class PactBrokerClientPactSpec extends Specification { } when: - def result = pactBroker.runTest { server, context -> + def result = imaginaryBroker.runTest { server, context -> assert pactBrokerClient.uploadPactFile(pactFile, '10.0.0') == 'FAILED! 400 Bad Request - Enjoy this bit of text' } then: - result instanceof PactVerificationResult.Ok + result == PactVerificationResult.Ok.INSTANCE } def 'pact broker navigation test'() { @@ -156,7 +187,7 @@ class PactBrokerClientPactSpec extends Specification { uponReceiving('a request to the root') withAttributes(path: '/') willRespondWith(status: 200) - withBody(mimeType: 'application/hal+json') { + withBody('application/hal+json') { '_links' { 'pb:latest-provider-pacts' { href url('http://localhost:8080', 'pacts', 'provider', '{provider}', 'latest') @@ -168,7 +199,7 @@ class PactBrokerClientPactSpec extends Specification { uponReceiving('a request for the provider pacts') withAttributes(path: '/pacts/provider/Activity Service/latest') willRespondWith(status: 200) - withBody(mimeType: 'application/hal+json') { + withBody('application/hal+json') { '_links' { provider { href url('http://localhost:8080', 'pacticipants', regexp('[^\\/]+', 'Activity Service')) @@ -191,6 +222,58 @@ class PactBrokerClientPactSpec extends Specification { } then: - result instanceof PactVerificationResult.Ok + result == PactVerificationResult.Ok.INSTANCE + } + + def 'publishing verification results pact test'() { + given: + pactBroker { + given('A pact has been published between the Provider and Foo Consumer') + uponReceiving('a pact publish verification request') + withAttributes(method: 'POST', + path: '/pacts/provider/Provider/consumer/Foo Consumer/pact-version/1234567890/verification-results', + body: [success: true, providerApplicationVersion: '10.0.0'] + ) + willRespondWith(status: 201) + } + + when: + def result = pactBroker.runTest { server, context -> + assert pactBrokerClient.publishVerificationResults([ + 'pb:publish-verification-results': [ + href: 'http://localhost:8080/pacts/provider/Provider/consumer/Foo%20Consumer/pact-version/1234567890' + + '/verification-results' + ] + ], TestResult.Ok.INSTANCE, '10.0.0') instanceof Ok + } + + then: + result == PactVerificationResult.Ok.INSTANCE + } + + def 'publishing verification results pact test with build info'() { + given: + pactBroker { + given('A pact has been published between the Provider and Foo Consumer') + uponReceiving('a pact publish verification request') + withAttributes(method: 'POST', + path: '/pacts/provider/Provider/consumer/Foo Consumer/pact-version/1234567890/verification-results', + body: [success: true, providerApplicationVersion: '10.0.0', buildUrl: 'http://localhost:8080/build'] + ) + willRespondWith(status: 201) + } + + when: + def result = pactBroker.runTest { server, context -> + assert pactBrokerClient.publishVerificationResults([ + 'pb:publish-verification-results': [ + href: 'http://localhost:8080/pacts/provider/Provider/consumer/Foo%20Consumer/pact-version/1234567890' + + '/verification-results' + ] + ], TestResult.Ok.INSTANCE, '10.0.0', 'http://localhost:8080/build') instanceof Ok + } + + then: + result == PactVerificationResult.Ok.INSTANCE } } diff --git a/provider/pact-jvm-provider-gradle/README.md b/provider/pact-jvm-provider-gradle/README.md index b6ca5bf9a6..55cc839dd6 100644 --- a/provider/pact-jvm-provider-gradle/README.md +++ b/provider/pact-jvm-provider-gradle/README.md @@ -544,6 +544,20 @@ pact { `pactBrokerUser` and `pactBrokerPassword` can be defined in the gradle properties. +Or with a bearer token: + +```groovy +pact { + + serviceProviders { + provider1 { + hasPactsFromPactBroker('http://pact-broker:5000/', authentication: ['Bearer', pactBrokerToken]) + } + } + +} +``` + ## Verifying pact files from a S3 bucket Pact files stored in an S3 bucket can be verified by using an S3 URL to the pact file. I.e., @@ -638,6 +652,19 @@ pact { } ``` +or with a bearer token + +```groovy +pact { + + publish { + pactBrokerUrl = 'https://mypactbroker.com' + pactBrokerToken = 'token' + } + +} +``` + ## Excluding pacts from being published You can exclude some of the pact files from being published by providing a list of regular expressions that match diff --git a/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublish.groovy b/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublish.groovy index 1fc4a45fe0..29c1258f22 100644 --- a/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublish.groovy +++ b/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublish.groovy @@ -13,7 +13,7 @@ class PactPublish { String pactBrokerToken String pactBrokerUsername String pactBrokerPassword - String pactBrokerAuthenticationScheme = 'basic' + String pactBrokerAuthenticationScheme List tags = [] List excludes = [] } diff --git a/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublishTask.groovy b/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublishTask.groovy index 9a116047ad..823f9c952f 100644 --- a/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublishTask.groovy +++ b/provider/pact-jvm-provider-gradle/src/main/groovy/au/com/dius/pact/provider/gradle/PactPublishTask.groovy @@ -33,7 +33,7 @@ class PactPublishTask extends DefaultTask { def options = [:] if (StringUtils.isNotEmpty(pactPublish.pactBrokerToken)) { - options.authentication = [pactPublish.pactBrokerAuthenticationScheme = 'bearer', + options.authentication = [pactPublish.pactBrokerAuthenticationScheme ?: 'bearer', pactPublish.pactBrokerToken] } else if (StringUtils.isNotEmpty(pactPublish.pactBrokerUsername)) { diff --git a/provider/pact-jvm-provider-gradle/src/test/groovy/au/com/dius/pact/provider/gradle/ProviderVerifierStateChangeSpec.groovy b/provider/pact-jvm-provider-gradle/src/test/groovy/au/com/dius/pact/provider/gradle/ProviderVerifierStateChangeSpec.groovy index 5d73a9ae88..d7879ec12c 100644 --- a/provider/pact-jvm-provider-gradle/src/test/groovy/au/com/dius/pact/provider/gradle/ProviderVerifierStateChangeSpec.groovy +++ b/provider/pact-jvm-provider-gradle/src/test/groovy/au/com/dius/pact/provider/gradle/ProviderVerifierStateChangeSpec.groovy @@ -6,6 +6,7 @@ import au.com.dius.pact.core.model.Request import au.com.dius.pact.core.model.RequestResponseInteraction import au.com.dius.pact.core.model.Response import au.com.dius.pact.provider.ConsumerInfo +import au.com.dius.pact.provider.DefaultStateChange import au.com.dius.pact.provider.ProviderClient import au.com.dius.pact.provider.ProviderInfo import au.com.dius.pact.provider.ProviderVerifier @@ -35,14 +36,15 @@ class ProviderVerifierStateChangeSpec extends Specification { def failures = [:] consumer = new ConsumerInfo('Bob', 'http://localhost:2000/hello') providerInfo.stateChangeTeardown = true - GroovyMock(StateChange, global: true) + def statechange = Mock(StateChange) + providerVerifier.stateChangeHandler = statechange when: providerVerifier.verifyInteraction(providerInfo, consumer, failures, interaction) then: - 1 * StateChange.executeStateChange(*_) >> new StateChangeResult(new Ok([:]), 'interactionMessage') - 1 * StateChange.executeStateChangeTeardown(providerVerifier, interaction, providerInfo, consumer, _) + 1 * statechange.executeStateChange(*_) >> new StateChangeResult(new Ok([:]), 'interactionMessage') + 1 * statechange.executeStateChangeTeardown(providerVerifier, interaction, providerInfo, consumer, _) } def 'if the state change is a closure and teardown is set, executes it with the state change as a parameter'() { @@ -59,9 +61,10 @@ class ProviderVerifierStateChangeSpec extends Specification { providerInfo.stateChangeTeardown = true when: - StateChange.executeStateChange(providerVerifier, providerInfo, consumer, interaction, 'state of the nation', - failures, providerClient) - StateChange.executeStateChangeTeardown(providerVerifier, interaction, providerInfo, consumer, providerClient) + DefaultStateChange.INSTANCE.executeStateChange(providerVerifier, providerInfo, consumer, interaction, + 'state of the nation', failures, providerClient) + DefaultStateChange.INSTANCE.executeStateChangeTeardown(providerVerifier, interaction, providerInfo, consumer, + providerClient) then: closureArgs == [[state, 'setup'], [state, 'teardown']] diff --git a/provider/pact-jvm-provider-junit/src/main/kotlin/au/com/dius/pact/provider/junit/target/HttpTarget.kt b/provider/pact-jvm-provider-junit/src/main/kotlin/au/com/dius/pact/provider/junit/target/HttpTarget.kt index 2f48c93393..336d91bb6e 100644 --- a/provider/pact-jvm-provider-junit/src/main/kotlin/au/com/dius/pact/provider/junit/target/HttpTarget.kt +++ b/provider/pact-jvm-provider-junit/src/main/kotlin/au/com/dius/pact/provider/junit/target/HttpTarget.kt @@ -110,11 +110,11 @@ open class HttpTarget override fun getProviderInfo(source: PactSource): ProviderInfo { val provider = testClass.getAnnotation(Provider::class.java) val providerInfo = ProviderInfo(provider.value) - providerInfo.setPort(port) - providerInfo.setHost(host) - providerInfo.setProtocol(protocol) - providerInfo.setPath(path) - providerInfo.isInsecure = insecure + providerInfo.port = port + providerInfo.host = host + providerInfo.protocol = protocol + providerInfo.path = path + providerInfo.insecure = insecure val methods = testClass.getAnnotatedMethods(TargetRequestFilter::class.java) if (!methods.isEmpty()) { diff --git a/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactJUnit5VerificationProvider.kt b/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactJUnit5VerificationProvider.kt index ecbbecb083..dda41d662f 100644 --- a/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactJUnit5VerificationProvider.kt +++ b/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactJUnit5VerificationProvider.kt @@ -4,6 +4,7 @@ import au.com.dius.pact.core.model.Interaction import au.com.dius.pact.core.model.Pact import au.com.dius.pact.core.model.ProviderState import au.com.dius.pact.core.model.RequestResponseInteraction +import au.com.dius.pact.core.pactbroker.TestResult import au.com.dius.pact.provider.ConsumerInfo import au.com.dius.pact.provider.DefaultTestResultAccumulator import au.com.dius.pact.provider.IProviderVerifier @@ -57,7 +58,7 @@ data class PactVerificationContext( var providerInfo: ProviderInfo = ProviderInfo(), val consumerName: String, val interaction: Interaction, - internal var testExecutionResult: Boolean = false + internal var testExecutionResult: TestResult = TestResult.Ok ) { var executionContext: Map? = null @@ -73,7 +74,7 @@ data class PactVerificationContext( val failures = mutableMapOf() try { this.testExecutionResult = validateTestExecution(client, request, failures) - if (!testExecutionResult) { + if (testExecutionResult is TestResult.Failed) { verifier!!.displayFailures(failures) throw AssertionError(JUnitProviderTestSupport.generateErrorStringFromMismatches(failures)) } @@ -82,7 +83,7 @@ data class PactVerificationContext( } } - private fun validateTestExecution(client: Any?, request: Any?, failures: MutableMap): Boolean { + private fun validateTestExecution(client: Any?, request: Any?, failures: MutableMap): TestResult { if (providerInfo.verificationType == null || providerInfo.verificationType == PactVerification.REQUEST_RESPONSE) { val interactionMessage = "Verifying a pact between $consumerName and ${providerInfo.name}" + " - ${interaction.description}" @@ -98,7 +99,7 @@ data class PactVerificationContext( it.requestFailed(providerInfo, interaction, interactionMessage, e, verifier!!.projectHasProperty.apply(ProviderVerifierBase.PACT_SHOW_STACKTRACE)) } - false + TestResult.Failed(listOf(e.message.orEmpty())) } } else { return verifier!!.verifyResponseByInvokingProviderMethods(providerInfo, ConsumerInfo(consumerName), interaction, diff --git a/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/TestTarget.kt b/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/TestTarget.kt index df0e5b7269..4f111a6329 100644 --- a/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/TestTarget.kt +++ b/provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/TestTarget.kt @@ -68,10 +68,10 @@ open class HttpTestTarget @JvmOverloads constructor ( override fun getProviderInfo(serviceName: String, pactSource: PactSource?): ProviderInfo { val providerInfo = ProviderInfo(serviceName) - providerInfo.setPort(port) - providerInfo.setHost(host) - providerInfo.setProtocol("http") - providerInfo.setPath(path) + providerInfo.port = port + providerInfo.host = host + providerInfo.protocol = "http" + providerInfo.path = path return providerInfo } @@ -120,8 +120,8 @@ open class HttpsTestTarget @JvmOverloads constructor ( override fun getProviderInfo(serviceName: String, pactSource: PactSource?): ProviderInfo { val providerInfo = super.getProviderInfo(serviceName, pactSource) - providerInfo.setProtocol("https") - providerInfo.isInsecure = insecure + providerInfo.protocol = "https" + providerInfo.insecure = insecure return providerInfo } diff --git a/provider/pact-jvm-provider-maven/README.md b/provider/pact-jvm-provider-maven/README.md index 204f0b114e..2f9e434ed9 100644 --- a/provider/pact-jvm-provider-maven/README.md +++ b/provider/pact-jvm-provider-maven/README.md @@ -620,6 +620,21 @@ For example: ``` +Or to use a bearer token: + +```xml + + au.com.dius + pact-jvm-provider-maven_2.12 + 3.5.11 + + http://pactbroker:1234 + TOKEN + Bearer + + +``` + #### Using the Maven servers configuration You can use the servers setup in the Maven settings. To do this, setup a server as per the @@ -645,17 +660,6 @@ against the base names of the pact files. For example: -```groovy -pact { - - publish { - pactBrokerUrl = 'https://mypactbroker.com' - excludes = [ '.*\\-\\d+$' ] // exclude all pact files that end with a dash followed by a number in the name - } - -} -``` - ```xml au.com.dius diff --git a/provider/pact-jvm-provider/build.gradle b/provider/pact-jvm-provider/build.gradle index 55d5e54cc5..1d24d186d1 100644 --- a/provider/pact-jvm-provider/build.gradle +++ b/provider/pact-jvm-provider/build.gradle @@ -6,6 +6,7 @@ dependencies { api project(":core:pact-jvm-core-model") api project(":core:pact-jvm-core-pact-broker") api project(":core:pact-jvm-core-matchers") + api project(":core:pact-jvm-core-support") compile'commons-io:commons-io:2.5', "org.fusesource.jansi:jansi:${project.jansiVersion}", "org.apache.httpcomponents:httpclient:${project.httpClientVersion}" diff --git a/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/HttpClientFactory.groovy b/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/HttpClientFactory.groovy deleted file mode 100644 index 6107df56d2..0000000000 --- a/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/HttpClientFactory.groovy +++ /dev/null @@ -1,84 +0,0 @@ -package au.com.dius.pact.provider - -import org.apache.http.config.Registry -import org.apache.http.config.RegistryBuilder -import org.apache.http.conn.socket.ConnectionSocketFactory -import org.apache.http.conn.socket.PlainConnectionSocketFactory -import org.apache.http.conn.ssl.AllowAllHostnameVerifier -import org.apache.http.conn.ssl.SSLConnectionSocketFactory -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.HttpClientBuilder -import org.apache.http.impl.client.HttpClients -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager -import org.apache.http.ssl.SSLContextBuilder - -import javax.net.ssl.HostnameVerifier -import javax.net.ssl.SSLContext -import java.security.cert.X509Certificate - -/** - * HTTP Client Factory - */ -@SuppressWarnings('FactoryMethodName') -class HttpClientFactory implements IHttpClientFactory { - - CloseableHttpClient newClient(def provider) { - if (provider?.createClient != null) { - if (provider.createClient instanceof Closure) { - provider.createClient(provider) - } else { - Binding binding = new Binding() - binding.setVariable('provider', provider) - GroovyShell shell = new GroovyShell(binding) - shell.evaluate(provider.createClient as String) - } - } else if (provider?.insecure) { - createInsecure() - } else if (provider?.trustStore && provider?.trustStorePassword) { - createWithTrustStore(provider) - } else { - HttpClients.createDefault() - } - } - - private static createWithTrustStore(provider) { - char[] password = provider.trustStorePassword.toCharArray() - - HttpClients - .custom() - .setSslcontext(new SSLContextBuilder().loadTrustMaterial(provider.trustStore as File, password).build()) - .build() - } - - private static CloseableHttpClient createInsecure() { - HttpClientBuilder b = HttpClientBuilder.create() - - // setup a Trust Strategy that allows all certificates. - // - def trustStratergy = { X509Certificate[] chain, String authType -> true } - SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStratergy).build() - b.setSslcontext(sslContext) - // don't check Hostnames, either. - // -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken - HostnameVerifier hostnameVerifier = new AllowAllHostnameVerifier() - - // here's the special part: - // -- need to create an SSL Socket Factory, to use our weakened "trust strategy"; - // -- and create a Registry, to register it. - // - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier) - Registry socketFactoryRegistry = RegistryBuilder. create() - .register('http', PlainConnectionSocketFactory.socketFactory) - .register('https', sslSocketFactory) - .build() - - // now, we create connection-manager using our Registry. - // -- allows multi-threaded use - PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry) - b.setConnectionManager(connMgr) - - // finally, build the HttpClient; - // -- done! - b.build() - } -} diff --git a/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/ProviderVerifier.groovy b/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/ProviderVerifier.groovy index 03eb55388c..a169026533 100644 --- a/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/ProviderVerifier.groovy +++ b/provider/pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/ProviderVerifier.groovy @@ -1,14 +1,12 @@ package au.com.dius.pact.provider -import au.com.dius.pact.com.github.michaelbull.result.Ok import au.com.dius.pact.core.model.FilteredPact import au.com.dius.pact.core.model.Interaction import au.com.dius.pact.core.model.Pact import au.com.dius.pact.core.model.PactReader -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.pactbroker.PactBrokerClient +import au.com.dius.pact.core.pactbroker.TestResult import groovy.util.logging.Slf4j import scala.Function1 @@ -54,9 +52,9 @@ class ProviderVerifier extends ProviderVerifierBase { if (pact.interactions.empty) { reporters.each { it.warnPactFileHasNoInteractions(pact) } } else { - boolean result = pact.interactions + def result = pact.interactions .collect(this.&verifyInteraction.curry(provider, consumer, failures)) - .inject(true) { acc, val -> acc && val } + .inject(TestResult.Ok.INSTANCE) { acc, val -> acc.merge(val) } if (pact.isFiltered()) { log.warn('Skipping publishing of verification results as the interactions have been filtered') } else if (publishingResultsDisabled()) { @@ -141,116 +139,8 @@ class ProviderVerifier extends ProviderVerifierBase { interaction.description ==~ projectGetProperty.apply(PACT_FILTER_DESCRIPTION) } - boolean verifyInteraction(ProviderInfo provider, ConsumerInfo consumer, Map failures, def interaction) { - def interactionMessage = "Verifying a pact between ${consumer.name} and ${provider.name}" + - " - ${interaction.description} " - - ProviderClient providerClient = new ProviderClient(provider, new HttpClientFactory()) - def stateChangeResult = StateChange.executeStateChange(this, provider, consumer, interaction, interactionMessage, - failures, providerClient) - if (stateChangeResult.stateChangeResult instanceof Ok) { - interactionMessage = stateChangeResult.message - reportInteractionDescription(interaction) - - Map context = [ - providerState: stateChangeResult.stateChangeResult.value, - interaction: interaction - ] - - boolean result = false - if (ProviderUtils.verificationType(provider, consumer) == PactVerification.REQUEST_RESPONSE) { - log.debug('Verifying via request/response') - result = verifyResponseFromProvider(provider, interaction, interactionMessage, failures, providerClient, - context) - } else { - log.debug('Verifying via annotated test method') - result = verifyResponseByInvokingProviderMethods(provider, consumer, interaction, interactionMessage, failures) - } - - if (provider.stateChangeTeardown) { - StateChange.executeStateChangeTeardown(this, interaction, provider, consumer, providerClient) - } - - result - } else { - false - } - } - - void reportInteractionDescription(interaction) { - reporters.each { it.interactionDescription(interaction) } - } - @Override void reportStateForInteraction(String state, IProviderInfo provider, IConsumerInfo consumer, boolean isSetup) { reporters.each { it.stateForInteraction(state, provider, consumer, isSetup) } } - - @SuppressWarnings('ParameterCount') - boolean verifyResponseFromProvider(IProviderInfo provider, RequestResponseInteraction interaction, - String interactionMessage, - Map failures, - ProviderClient client, - Map context = [:]) { - try { - def expectedResponse = interaction.response.generatedResponse(context) - def actualResponse = client.makeRequest(interaction.request.generatedRequest(context)) - - verifyRequestResponsePact(expectedResponse, actualResponse, interactionMessage, failures) - } catch (e) { - failures[interactionMessage] = e - reporters.each { - it.requestFailed(provider, interaction, interactionMessage, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE)) - } - false - } - } - - boolean verifyRequestResponsePact(Response expectedResponse, Map actualResponse, String interactionMessage, - Map failures) { - def comparison = ResponseComparison.compareResponse(expectedResponse, actualResponse, - actualResponse.statusCode, actualResponse.headers, actualResponse.data) - - reporters.each { it.returnsAResponseWhich() } - - def s = ' returns a response which' - def result = true - result &= displayStatusResult(failures, expectedResponse.status, comparison.method, interactionMessage + s) - result &= displayHeadersResult(failures, expectedResponse.headers, comparison.headers, interactionMessage + s) - result &= displayBodyResult(failures, comparison.body, interactionMessage + s) - result - } - - boolean displayStatusResult(Map failures, int status, def comparison, String comparisonDescription) { - if (comparison == null) { - reporters.each { it.statusComparisonOk(status) } - true - } else { - reporters.each { it.statusComparisonFailed(status, comparison) } - failures["$comparisonDescription has status code $status"] = comparison - false - } - } - - boolean displayHeadersResult(Map failures, def expected, Map comparison, String comparisonDescription) { - if (comparison.isEmpty()) { - true - } else { - reporters.each { it.includesHeaders() } - Map expectedHeaders = expected - boolean result = true - comparison.each { key, headerComparison -> - def expectedHeaderValue = expectedHeaders[key] - if (headerComparison == null) { - reporters.each { it.headerComparisonOk(key, expectedHeaderValue) } - } else { - reporters.each { it.headerComparisonFailed(key, expectedHeaderValue, headerComparison) } - failures["$comparisonDescription includes headers \"$key\" with value \"$expectedHeaderValue\""] = - headerComparison - result = false - } - } - result - } - } } diff --git a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt new file mode 100644 index 0000000000..d67290e1ec --- /dev/null +++ b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt @@ -0,0 +1,82 @@ +package au.com.dius.pact.provider + +import groovy.lang.Binding +import groovy.lang.Closure +import groovy.lang.GroovyShell +import org.apache.http.config.RegistryBuilder +import org.apache.http.conn.socket.ConnectionSocketFactory +import org.apache.http.conn.socket.PlainConnectionSocketFactory +import org.apache.http.conn.ssl.AllowAllHostnameVerifier +import org.apache.http.conn.ssl.SSLConnectionSocketFactory +import org.apache.http.impl.client.CloseableHttpClient +import org.apache.http.impl.client.HttpClientBuilder +import org.apache.http.impl.client.HttpClients +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager +import org.apache.http.ssl.SSLContextBuilder +import org.apache.http.ssl.TrustStrategy +import java.security.cert.X509Certificate + +/** + * HTTP Client Factory + */ +class HttpClientFactory: IHttpClientFactory { + + override fun newClient(provider: IProviderInfo): CloseableHttpClient { + return if (provider.createClient != null) { + if (provider.createClient is Closure<*>) { + (provider.createClient as Closure<*>).call(provider) as CloseableHttpClient + } else { + val binding = Binding() + binding.setVariable("provider", provider) + val shell = GroovyShell(binding) + shell.evaluate(provider.createClient.toString()) as CloseableHttpClient + } + } else if (provider.insecure) { + createInsecure() + } else if (provider.trustStore != null && provider.trustStorePassword != null) { + createWithTrustStore(provider) + } else { + HttpClients.createDefault() + } + } + + private fun createWithTrustStore(provider: IProviderInfo): CloseableHttpClient { + val password = provider.trustStorePassword.orEmpty().toCharArray() + return HttpClients + .custom() + .setSslcontext(SSLContextBuilder().loadTrustMaterial(provider.trustStore, password).build()) + .build() + } + + private fun createInsecure(): CloseableHttpClient { + val b = HttpClientBuilder.create() + + // setup a Trust Strategy that allows all certificates. + // + val trustStratergy = TrustStrategy { _: Array, _: String -> true } + val sslContext = SSLContextBuilder().loadTrustMaterial(null, trustStratergy).build() + b.setSslcontext(sslContext) + // don't check Hostnames, either. + // -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken + val hostnameVerifier = AllowAllHostnameVerifier() + + // here's the special part: + // -- need to create an SSL Socket Factory, to use our weakened "trust strategy"; + // -- and create a Registry, to register it. + // + val sslSocketFactory = SSLConnectionSocketFactory(sslContext, hostnameVerifier) + val socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build() + + // now, we create connection-manager using our Registry. + // -- allows multi-threaded use + val connMgr = PoolingHttpClientConnectionManager(socketFactoryRegistry) + b.setConnectionManager(connMgr) + + // finally, build the HttpClient; + // -- done! + return b.build() + } +} diff --git a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderClient.kt b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderClient.kt index cf51d91182..0b0d19d14d 100644 --- a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderClient.kt +++ b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderClient.kt @@ -46,15 +46,15 @@ import java.util.function.Function import java.util.function.Supplier interface IHttpClientFactory { - fun newClient(provider: Any?): CloseableHttpClient + fun newClient(provider: IProviderInfo): CloseableHttpClient } interface IProviderInfo { - val protocol: String - val host: Any? - val port: Any? - val path: String - val name: String + var protocol: String + var host: Any? + var port: Any? + var path: String + var name: String val requestFilter: Any? val stateChangeRequestFilter: Any? @@ -63,6 +63,11 @@ interface IProviderInfo { val stateChangeTeardown: Boolean var packagesToScan: List var verificationType: PactVerification? + var createClient: Any? + + var insecure: Boolean + var trustStore: File? + var trustStorePassword: String? } interface IConsumerInfo { diff --git a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt index 93e9c3868d..af1efc26dc 100644 --- a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt +++ b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/ProviderVerifier.kt @@ -1,7 +1,6 @@ package au.com.dius.pact.provider -import au.com.dius.pact.com.github.michaelbull.result.Err -import au.com.dius.pact.core.model.BrokerUrlSource +import au.com.dius.pact.com.github.michaelbull.result.Ok import au.com.dius.pact.core.model.Interaction import au.com.dius.pact.core.model.OptionalBody import au.com.dius.pact.core.model.Pact @@ -10,12 +9,12 @@ import au.com.dius.pact.core.model.RequestResponseInteraction import au.com.dius.pact.core.model.Response 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.provider.reporters.AnsiConsoleReporter import au.com.dius.pact.provider.reporters.VerifierReporter import groovy.lang.GroovyObjectSupport import io.github.classgraph.ClassGraph import mu.KLogging -import mu.KotlinLogging import java.lang.reflect.Method import java.net.URL import java.net.URLClassLoader @@ -23,50 +22,11 @@ import java.util.function.BiConsumer import java.util.function.Function import java.util.function.Supplier -private val logger = KotlinLogging.logger {} - -interface VerificationReporter { - fun reportResults(pact: Pact, result: Boolean, version: String, client: PactBrokerClient? = null) - where I : Interaction - - /** - * This must return true unless the pact.verifier.publishResults property has the value of "true" - */ - fun publishingResultsDisabled(): Boolean -} - @JvmOverloads @Deprecated("Use the VerificationReporter instead of this function", ReplaceWith("DefaultVerificationReporter.reportResults(pact, result, version, client)")) fun reportVerificationResults(pact: Pact, result: Boolean, version: String, client: PactBrokerClient? = null) - where I : Interaction = DefaultVerificationReporter.reportResults(pact, result, version, client) - -object DefaultVerificationReporter : VerificationReporter { - override fun reportResults(pact: Pact, result: Boolean, version: String, client: PactBrokerClient?) - where I : Interaction { - val source = pact.source - when (source) { - is BrokerUrlSource -> { - val brokerClient = client ?: PactBrokerClient(source.pactBrokerUrl, source.options) - publishResult(brokerClient, source, result, version, pact) - } - else -> logger.info { "Skipping publishing verification results for source $source" } - } - } - - private fun publishResult(brokerClient: PactBrokerClient, source: BrokerUrlSource, result: Boolean, version: String, pact: Pact) where I : Interaction { - val publishResult = brokerClient.publishVerificationResults(source.attributes, result, version) - if (publishResult is Err) { - logger.error { "Failed to publish verification results - ${publishResult.error.localizedMessage}" } - logger.debug(publishResult.error) {} - } else { - logger.info { "Published verification result of '$result' for consumer '${pact.consumer}'" } - } - } - - override fun publishingResultsDisabled() = - System.getProperty(ProviderVerifierBase.PACT_VERIFIER_PUBLISH_RESULTS)?.toLowerCase() != "true" -} + where I : Interaction = DefaultVerificationReporter.reportResults(pact, TestResult.fromBoolean(result), version, client) enum class PactVerification { REQUEST_RESPONSE, ANNOTATED_METHOD @@ -143,6 +103,17 @@ interface IProviderVerifier { */ 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 */ @@ -152,8 +123,8 @@ interface IProviderVerifier { interactionMessage: String, failures: MutableMap, client: ProviderClient, - context: Map = emptyMap() - ): Boolean + context: Map + ): TestResult /** * Verifies the interaction by invoking a method on a provider test class @@ -164,7 +135,7 @@ interface IProviderVerifier { interaction: Interaction, interactionMessage: String, failures: MutableMap - ): Boolean + ): TestResult /** * Compares the expected and actual responses @@ -174,12 +145,17 @@ interface IProviderVerifier { actualResponse: Map, interactionMessage: String, failures: MutableMap - ): Boolean + ): 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) } /** @@ -198,6 +174,7 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( override var projectHasProperty = Function { name -> !System.getProperty(name).isNullOrEmpty() } var projectGetProperty = Function { name -> System.getProperty(name) } var verificationReporter: VerificationReporter = DefaultVerificationReporter + var stateChangeHandler: StateChange = DefaultStateChange /** * This will return true unless the pact.verifier.publishResults property has the value of "true" @@ -213,7 +190,7 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( interaction: Interaction, interactionMessage: String, failures: MutableMap - ): Boolean { + ): TestResult { try { val urls = projectClasspath.get() logger.debug { "projectClasspath = $urls" } @@ -258,10 +235,10 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( verifyMessagePact(methodsAnnotatedWith.toHashSet(), interaction, interactionMessage, failures) } else { val expectedResponse = (interaction as RequestResponseInteraction).response - var result = true + var result: TestResult = TestResult.Ok methodsAnnotatedWith.forEach { val actualResponse = invokeProviderMethod(it, null) as Map - result = result && this.verifyRequestResponsePact(expectedResponse, actualResponse, interactionMessage, failures) + result = result.merge(this.verifyRequestResponsePact(expectedResponse, actualResponse, interactionMessage, failures)) } result } @@ -269,23 +246,23 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( } catch (e: Exception) { failures[interactionMessage] = e reporters.forEach { it.verificationFailed(interaction, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE)) } - return false + return TestResult.Failed(listOf(e.message.orEmpty())) } } - fun displayBodyResult(failures: MutableMap, comparison: Map, comparisonDescription: String): Boolean { + fun displayBodyResult(failures: MutableMap, comparison: Map, comparisonDescription: String): TestResult { return if (comparison.isEmpty()) { reporters.forEach { it.bodyComparisonOk() } - true + TestResult.Ok } else { reporters.forEach { it.bodyComparisonFailed(comparison) } failures["$comparisonDescription has a matching body"] = comparison - false + TestResult.Failed(listOf(comparison)) } } - fun verifyMessagePact(methods: Set, message: Message, interactionMessage: String, failures: MutableMap): Boolean { - var result = true + 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)) @@ -310,9 +287,9 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( } val comparison = ResponseComparison.compareMessage(message, actualMessage, messageMetadata) val s = " generates a message which" - result = result && displayBodyResult(failures, comparison["body"] as Map, interactionMessage + s) && - displayMetadataResult(messageMetadata ?: emptyMap(), failures, comparison["metadata"] as Map, - interactionMessage + s) + result = result.merge(displayBodyResult(failures, comparison["body"] as Map, interactionMessage + s)) + .merge(displayMetadataResult(messageMetadata ?: emptyMap(), failures, comparison["metadata"] as Map, + interactionMessage + s)) } return result } @@ -322,13 +299,13 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( failures: MutableMap, comparison: Map, comparisonDescription: String - ): Boolean { + ): TestResult { return if (comparison.isEmpty()) { reporters.forEach { it.metadataComparisonOk() } - true + TestResult.Ok } else { reporters.forEach { it.includesMetadata() } - var result = true + var result: TestResult = TestResult.Ok comparison.forEach { (key, metadataComparison) -> val expectedValue = expectedMetadata[key] if (metadataComparison == null) { @@ -337,7 +314,7 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( reporters.forEach { it.metadataComparisonFailed(key, expectedValue, metadataComparison) } failures["$comparisonDescription includes metadata \"$key\" with value \"$expectedValue\""] = metadataComparison - result = false + result = result.merge(TestResult.Failed(listOf(key to metadataComparison))) } } result @@ -352,6 +329,140 @@ abstract class ProviderVerifierBase @JvmOverloads constructor ( reporters.forEach { it.finaliseReport() } } + fun verifyInteraction( + provider: IProviderInfo, + consumer: IConsumerInfo, + failures: MutableMap, + interaction: Interaction + ): TestResult { + var interactionMessage = "Verifying a pact between ${consumer.name} and ${provider.name}" + + " - ${interaction.description} " + + val providerClient = ProviderClient(provider, HttpClientFactory()) + 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("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 + ): 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["method"], interactionMessage + s) + .merge(displayHeadersResult(failures, expectedResponse.headers ?: mapOf(), comparison["headers"] as Map, interactionMessage + s)) + .merge(displayBodyResult(failures, comparison["body"] as Map, interactionMessage + s)) + } + + fun displayStatusResult( + failures: MutableMap, + status: Int, + comparison: Any?, + comparisonDescription: String + ): TestResult { + return if (comparison == null) { + reporters.forEach { it.statusComparisonOk(status) } + TestResult.Ok + } else { + reporters.forEach { it.statusComparisonFailed(status, comparison) } + failures["$comparisonDescription has status code $status"] = comparison + TestResult.Failed(listOf(comparison.toString())) + } + } + + fun displayHeadersResult( + failures: MutableMap, + expected: Map>, + comparison: Map, + comparisonDescription: String + ): TestResult { + return if (comparison.isEmpty()) { + TestResult.Ok + } else { + reporters.forEach { it.includesHeaders() } + var result: TestResult = TestResult.Ok + comparison.forEach { (key, headerComparison) -> + val expectedHeaderValue = expected[key] + if (headerComparison == null) { + reporters.forEach { it.headerComparisonOk(key, expectedHeaderValue!!) } + } else { + reporters.forEach { it.headerComparisonFailed(key, expectedHeaderValue!!, headerComparison) } + failures["$comparisonDescription includes headers \"$key\" with value \"$expectedHeaderValue\""] = + headerComparison + result = result.merge(TestResult.Failed(listOf(headerComparison))) + } + } + 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) + } catch (e: Exception) { + failures[interactionMessage] = e + reporters.forEach { + it.requestFailed(provider, interaction, interactionMessage, e, projectHasProperty.apply(PACT_SHOW_STACKTRACE)) + } + TestResult.Failed(listOf(e.message.orEmpty())) + } + } + companion object : KLogging() { const val PACT_VERIFIER_PUBLISH_RESULTS = "pact.verifier.publishResults" const val PACT_FILTER_CONSUMERS = "pact.filter.consumers" diff --git a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/StateChange.kt b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/StateChange.kt index c2e92ff57e..645ced4066 100644 --- a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/StateChange.kt +++ b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/StateChange.kt @@ -22,13 +22,41 @@ data class StateChangeResult @JvmOverloads constructor ( val message: String = "" ) +interface StateChange { + fun executeStateChange( + verifier: IProviderVerifier, + provider: IProviderInfo, + consumer: IConsumerInfo, + interaction: Interaction, + interactionMessage: String, + failures: MutableMap, + providerClient: ProviderClient + ): StateChangeResult + + fun stateChange( + verifier: IProviderVerifier, + state: ProviderState, + provider: IProviderInfo, + consumer: IConsumerInfo, + isSetup: Boolean, + providerClient: ProviderClient + ): Result, Exception> + + fun executeStateChangeTeardown( + verifier: IProviderVerifier, + interaction: Interaction, + provider: IProviderInfo, + consumer: IConsumerInfo, + providerClient: ProviderClient + ) +} + /** * Class containing all the state change logic */ -object StateChange : KLogging() { +object DefaultStateChange : StateChange, KLogging() { - @JvmStatic - fun executeStateChange( + override fun executeStateChange( verifier: IProviderVerifier, provider: IProviderInfo, consumer: IConsumerInfo, @@ -66,8 +94,7 @@ object StateChange : KLogging() { return StateChangeResult(stateChangeResult, message) } - @JvmStatic - fun stateChange( + override fun stateChange( verifier: IProviderVerifier, state: ProviderState, provider: IProviderInfo, @@ -113,8 +140,7 @@ object StateChange : KLogging() { } } - @JvmStatic - fun executeStateChangeTeardown( + override fun executeStateChangeTeardown( verifier: IProviderVerifier, interaction: Interaction, provider: IProviderInfo, diff --git a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/TestResultAccumulator.kt b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/TestResultAccumulator.kt index 9858a2694c..ed9188b8eb 100644 --- a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/TestResultAccumulator.kt +++ b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/TestResultAccumulator.kt @@ -2,23 +2,36 @@ package au.com.dius.pact.provider import au.com.dius.pact.core.model.Interaction import au.com.dius.pact.core.model.Pact +import au.com.dius.pact.core.pactbroker.TestResult import au.com.dius.pact.provider.ProviderVerifierBase.Companion.PACT_VERIFIER_PUBLISH_RESULTS import mu.KLogging import org.apache.commons.lang3.builder.HashCodeBuilder +/** + * Accumulates the test results for the interactions. Once all the interactions for a pact have been verified, + * the result is submitted back to the broker + */ interface TestResultAccumulator { - fun updateTestResult(pact: Pact, interaction: Interaction, testExecutionResult: Boolean) + @Deprecated(message = "Use the version that takes a TestResult parameter") + fun updateTestResult(pact: Pact, interaction: Interaction, testExecutionResult: Boolean) + fun updateTestResult(pact: Pact, interaction: Interaction, testExecutionResult: TestResult) + + fun clearTestResult(pact: Pact) } object DefaultTestResultAccumulator : TestResultAccumulator, KLogging() { - private val testResults: MutableMap> = mutableMapOf() + private val testResults: MutableMap> = mutableMapOf() var verificationReporter: VerificationReporter = DefaultVerificationReporter + override fun updateTestResult(pact: Pact, interaction: Interaction, testExecutionResult: Boolean) { + updateTestResult(pact, interaction, TestResult.fromBoolean(testExecutionResult)) + } + override fun updateTestResult( - pact: Pact, + pact: Pact, interaction: Interaction, - testExecutionResult: Boolean + testExecutionResult: TestResult ) { logger.debug { "Received test result '$testExecutionResult' for Pact ${pact.provider.name}-${pact.consumer.name} " + "and ${interaction.description}" } @@ -32,8 +45,9 @@ object DefaultTestResultAccumulator : TestResultAccumulator, KLogging() { logger.warn { "Skipping publishing of verification results as it has been disabled " + "($PACT_VERIFIER_PUBLISH_RESULTS is not 'true')" } } else { - verificationReporter.reportResults(pact, - interactionResults.values.fold(true) { acc, result -> acc && result }, lookupProviderVersion()) + verificationReporter.reportResults(pact, interactionResults.values.fold(TestResult.Ok) { + acc: TestResult, result -> acc.merge(result) + }, lookupProviderVersion()) } } } @@ -44,7 +58,7 @@ object DefaultTestResultAccumulator : TestResultAccumulator, KLogging() { return builder.toHashCode() } - fun calculatePactHash(pact: Pact) = + fun calculatePactHash(pact: Pact) = HashCodeBuilder().append(pact.consumer.name).append(pact.provider.name).toHashCode() fun lookupProviderVersion(): String { @@ -57,7 +71,12 @@ object DefaultTestResultAccumulator : TestResultAccumulator, KLogging() { } } - fun allInteractionsVerified(pact: Pact, results: MutableMap): Boolean { + fun allInteractionsVerified(pact: Pact, results: MutableMap): Boolean { return pact.interactions.all { results.containsKey(calculateInteractionHash(it)) } } + + override fun clearTestResult(pact: Pact) { + val pactHash = calculatePactHash(pact) + testResults.remove(pactHash) + } } diff --git a/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt new file mode 100644 index 0000000000..cdf0044ccd --- /dev/null +++ b/provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/VerificationReporter.kt @@ -0,0 +1,56 @@ +package au.com.dius.pact.provider + +import au.com.dius.pact.com.github.michaelbull.result.Err +import au.com.dius.pact.core.model.BrokerUrlSource +import au.com.dius.pact.core.model.Interaction +import au.com.dius.pact.core.model.Pact +import au.com.dius.pact.core.pactbroker.PactBrokerClient +import au.com.dius.pact.core.pactbroker.TestResult +import mu.KLogging + +interface VerificationReporter { + @Deprecated(message = "Use the method that takes a test result") + fun reportResults(pact: Pact, result: Boolean, version: String, client: PactBrokerClient? = null) + fun reportResults(pact: Pact, result: TestResult, version: String, client: PactBrokerClient? = null) + + /** + * This must return true unless the pact.verifier.publishResults property has the value of "true" + */ + fun publishingResultsDisabled(): Boolean +} + +object DefaultVerificationReporter : VerificationReporter, KLogging() { + + override fun reportResults(pact: Pact, result: Boolean, version: String, client: PactBrokerClient?) { + reportResults(pact, TestResult.fromBoolean(result), version, client) + } + + override fun reportResults(pact: Pact, result: TestResult, version: String, client: PactBrokerClient?) { + when (val source = pact.source) { + is BrokerUrlSource -> { + val brokerClient = client ?: PactBrokerClient(source.pactBrokerUrl, source.options) + publishResult(brokerClient, source, result, version, pact) + } + else -> logger.info { "Skipping publishing verification results for source $source" } + } + } + + private fun publishResult( + brokerClient: PactBrokerClient, + source: BrokerUrlSource, + result: TestResult, + version: String, + pact: Pact + ) where I : Interaction { + val publishResult = brokerClient.publishVerificationResults(source.attributes, result, version) + if (publishResult is Err) { + logger.error { "Failed to publish verification results - ${publishResult.error.localizedMessage}" } + logger.debug(publishResult.error) {} + } else { + logger.info { "Published verification result of '$result' for consumer '${pact.consumer}'" } + } + } + + override fun publishingResultsDisabled() = + System.getProperty(ProviderVerifierBase.PACT_VERIFIER_PUBLISH_RESULTS)?.toLowerCase() != "true" +} diff --git a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy new file mode 100644 index 0000000000..907ba253fa --- /dev/null +++ b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy @@ -0,0 +1,32 @@ +package au.com.dius.pact.provider + +import org.apache.http.impl.client.CloseableHttpClient +import spock.lang.Specification + +class HttpClientFactorySpec extends Specification { + + def 'creates a new client by default'() { + expect: + new HttpClientFactory().newClient(new ProviderInfo()) != null + } + + def 'if createClient is provided as a closure, invokes that'() { + given: + def provider = new ProviderInfo() + def httpClient = Mock(CloseableHttpClient) + provider.createClient = { httpClient } + + expect: + new HttpClientFactory().newClient(provider) == httpClient + } + + def 'if createClient is provided as a string, invokes t`hat as Groovy code'() { + given: + def provider = new ProviderInfo() + provider.createClient = '[:] as org.apache.http.impl.client.CloseableHttpClient' + + expect: + new HttpClientFactory().newClient(provider) != null + } + +} diff --git a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/ProviderVerifierSpec.groovy b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/ProviderVerifierSpec.groovy index 62d2615093..5d69922bc4 100644 --- a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/ProviderVerifierSpec.groovy +++ b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/ProviderVerifierSpec.groovy @@ -13,6 +13,7 @@ import au.com.dius.pact.core.model.RequestResponsePact import au.com.dius.pact.core.model.UnknownPactSource import au.com.dius.pact.core.model.UrlSource import au.com.dius.pact.core.model.messaging.Message +import au.com.dius.pact.core.pactbroker.TestResult import au.com.dius.pact.core.pactbroker.PactBrokerClient import au.com.dius.pact.provider.reporters.VerifierReporter import au.com.dius.pact.com.github.michaelbull.result.Ok @@ -368,7 +369,9 @@ class ProviderVerifierSpec extends Specification { ConsumerInfo consumer = new ConsumerInfo(name: 'Test Consumer', pactSource: UnknownPactSource.INSTANCE) PactBrokerClient pactBrokerClient = Mock(PactBrokerClient, constructorArgs: ['']) GroovyMock(PactReader, global: true) - GroovyMock(StateChange, global: true) + def statechange = Mock(StateChange) { + executeStateChange(*_) >> new StateChangeResult(new Ok([:])) + } def interaction1 = Mock(RequestResponseInteraction) def interaction2 = Mock(RequestResponseInteraction) def mockPact = Mock(Pact) { @@ -379,10 +382,10 @@ class ProviderVerifierSpec extends Specification { verifier.projectGetProperty = { (it == ProviderVerifierBase.PACT_VERIFIER_PUBLISH_RESULTS).toString() } + verifier.stateChangeHandler = statechange PactReader.loadPact(_) >> mockPact mockPact.interactions >> [interaction1, interaction2] - StateChange.executeStateChange(*_) >> new StateChangeResult(new Ok([:])) when: verifier.runVerificationForConsumer([:], provider, consumer, pactBrokerClient) @@ -394,20 +397,22 @@ class ProviderVerifierSpec extends Specification { where: - result1 | result2 | finalResult - true | true | true - true | false | false - false | true | false - false | false | false + result1 | result2 | finalResult + TestResult.Ok.INSTANCE | TestResult.Ok.INSTANCE | TestResult.Ok.INSTANCE + TestResult.Ok.INSTANCE | new TestResult.Failed() | new TestResult.Failed() + new TestResult.Failed() | TestResult.Ok.INSTANCE | new TestResult.Failed() + new TestResult.Failed() | new TestResult.Failed() | new TestResult.Failed() } @SuppressWarnings('UnnecessaryGetter') - def 'Do not publish verification results if the pact interactions have been filtered'() { + def 'Do not publish verification results if not all the pact interactions have been verified'() { given: ProviderInfo provider = new ProviderInfo('Test Provider') ConsumerInfo consumer = new ConsumerInfo(name: 'Test Consumer', pactSource: UnknownPactSource.INSTANCE) GroovyMock(PactReader, global: true) - GroovyMock(StateChange, global: true) + def statechange = Mock(StateChange) { + executeStateChange(*_) >> new StateChangeResult(new Ok([:])) + } def interaction1 = Mock(RequestResponseInteraction) { getDescription() >> 'Interaction 1' } @@ -420,13 +425,13 @@ class ProviderVerifierSpec extends Specification { PactReader.loadPact(_) >> mockPact mockPact.interactions >> [interaction1, interaction2] - StateChange.executeStateChange(*_) >> new StateChangeResult(new Ok([:])) verifier.verifyResponseFromProvider(provider, interaction1, _, _, _) >> true verifier.verifyResponseFromProvider(provider, interaction2, _, _, _) >> true verifier.projectHasProperty = { it == ProviderVerifier.PACT_FILTER_DESCRIPTION } verifier.projectGetProperty = { 'Interaction 2' } verifier.verificationReporter = Mock(VerificationReporter) + verifier.stateChangeHandler = statechange when: verifier.runVerificationForConsumer([:], provider, consumer) @@ -445,10 +450,10 @@ class ProviderVerifierSpec extends Specification { def client = Mock(PactBrokerClient) when: - DefaultVerificationReporter.INSTANCE.reportResults(pact, true, '0', client) + DefaultVerificationReporter.INSTANCE.reportResults(pact, TestResult.Ok.INSTANCE, '0', client) then: - 1 * client.publishVerificationResults(links, true, '0', null) >> new Ok(true) + 1 * client.publishVerificationResults(links, TestResult.Ok.INSTANCE, '0', null) >> new Ok(true) } @SuppressWarnings('UnnecessaryGetter') @@ -460,18 +465,18 @@ class ProviderVerifierSpec extends Specification { def client = Mock(PactBrokerClient) when: - DefaultVerificationReporter.INSTANCE.reportResults(pact, true, '0', client) + DefaultVerificationReporter.INSTANCE.reportResults(pact, TestResult.Ok.INSTANCE, '0', client) then: - 0 * client.publishVerificationResults(_, true, '0', null) + 0 * client.publishVerificationResults(_, TestResult.Ok.INSTANCE, '0', null) } - @SuppressWarnings('UnnecessaryGetter') + @SuppressWarnings(['UnnecessaryGetter', 'LineLength']) def 'Ignore the verification results if publishing is disabled'() { given: def client = Mock(PactBrokerClient) GroovyMock(PactReader, global: true) - GroovyMock(StateChange, global: true) + def statechange = Mock(StateChange) def providerInfo = new ProviderInfo(verificationType: PactVerification.ANNOTATED_METHOD) def consumerInfo = new ConsumerInfo() @@ -489,15 +494,16 @@ class ProviderVerifierSpec extends Specification { return 'false' } } + verifier.stateChangeHandler = statechange when: verifier.runVerificationForConsumer([:], providerInfo, consumerInfo, client) then: 1 * PactReader.loadPact(_) >> pact - 1 * StateChange.executeStateChange(_, _, _, _, _, _, _) >> new StateChangeResult(new Ok([:]), '') - 1 * verifier.verifyResponseByInvokingProviderMethods(providerInfo, consumerInfo, interaction, _, _) >> true - 0 * client.publishVerificationResults(_, true, _, _) + 1 * statechange.executeStateChange(_, _, _, _, _, _, _) >> new StateChangeResult(new Ok([:]), '') + 1 * verifier.verifyResponseByInvokingProviderMethods(providerInfo, consumerInfo, interaction, _, _) >> TestResult.Ok.INSTANCE + 0 * client.publishVerificationResults(_, TestResult.Ok.INSTANCE, _, _) } @Unroll diff --git a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/StateChangeSpec.groovy b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/StateChangeSpec.groovy index 17e92cf3a1..7b9f14567a 100644 --- a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/StateChangeSpec.groovy +++ b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/StateChangeSpec.groovy @@ -36,7 +36,7 @@ class StateChangeSpec extends Specification { consumerMap.stateChange = null when: - def result = StateChange.stateChange(providerVerifier, state, providerInfo, consumer(), true, + def result = DefaultStateChange.INSTANCE.stateChange(providerVerifier, state, providerInfo, consumer(), true, mockProviderClient) then: @@ -49,7 +49,7 @@ class StateChangeSpec extends Specification { consumerMap.stateChange = '' when: - def result = StateChange.stateChange(providerVerifier, state, providerInfo, consumer(), true, + def result = DefaultStateChange.INSTANCE.stateChange(providerVerifier, state, providerInfo, consumer(), true, mockProviderClient) then: @@ -62,7 +62,7 @@ class StateChangeSpec extends Specification { consumerMap.stateChange = ' ' when: - def result = StateChange.stateChange(providerVerifier, state, providerInfo, consumer(), true, + def result = DefaultStateChange.INSTANCE.stateChange(providerVerifier, state, providerInfo, consumer(), true, mockProviderClient) then: @@ -75,7 +75,7 @@ class StateChangeSpec extends Specification { consumerMap.stateChange = 'http://localhost:2000/hello' when: - def result = StateChange.stateChange(providerVerifier, state, providerInfo, consumer(), true, + def result = DefaultStateChange.INSTANCE.stateChange(providerVerifier, state, providerInfo, consumer(), true, mockProviderClient) then: @@ -91,7 +91,7 @@ class StateChangeSpec extends Specification { consumerMap.stateChange = { arg -> closureArgs << arg; true } when: - def result = StateChange.stateChange(providerVerifier, state, providerInfo, consumer(), true, + def result = DefaultStateChange.INSTANCE.stateChange(providerVerifier, state, providerInfo, consumer(), true, mockProviderClient) then: @@ -105,7 +105,7 @@ class StateChangeSpec extends Specification { consumerMap.stateChange = 'blah blah blah' when: - def result = StateChange.stateChange(providerVerifier, state, providerInfo, consumer(), true, + def result = DefaultStateChange.INSTANCE.stateChange(providerVerifier, state, providerInfo, consumer(), true, mockProviderClient) then: @@ -123,7 +123,7 @@ class StateChangeSpec extends Specification { ] as Interaction when: - def result = StateChange.executeStateChange(providerVerifier, providerInfo, consumer(), interaction, + def result = DefaultStateChange.INSTANCE.executeStateChange(providerVerifier, providerInfo, consumer(), interaction, '', [:], mockProviderClient) then: diff --git a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy index 589baab75c..9384f0d4eb 100644 --- a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy +++ b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/TestResultAccumulatorSpec.groovy @@ -5,6 +5,7 @@ import au.com.dius.pact.core.model.Provider 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.pactbroker.TestResult import spock.lang.Specification import spock.lang.Unroll import spock.util.environment.RestoreSystemProperties @@ -71,7 +72,7 @@ class TestResultAccumulatorSpec extends Specification { testResultAccumulator.updateTestResult(mutablePact, interaction3, true) then: - 1 * mockVerificationReporter.reportResults(_, true, _, null) + 1 * mockVerificationReporter.reportResults(_, TestResult.Ok.INSTANCE, _, null) cleanup: testResultAccumulator.verificationReporter = DefaultVerificationReporter.INSTANCE @@ -117,11 +118,11 @@ class TestResultAccumulatorSpec extends Specification { where: - result << [true, false] + result << [TestResult.Ok.INSTANCE, new TestResult.Failed()] } @Unroll - def 'updateTestResult - publish verification results should be an or of all the test results'() { + def 'updateTestResult - publish verification results should be an "or" of all the test results'() { given: def pact = new RequestResponsePact(new Provider('provider'), new Consumer('consumer'), [interaction1, interaction2]) @@ -143,11 +144,11 @@ class TestResultAccumulatorSpec extends Specification { where: - interaction1Result | interaction2Result | result - true | true | true - true | false | false - false | true | false - false | false | false + interaction1Result | interaction2Result | result + TestResult.Ok.INSTANCE | TestResult.Ok.INSTANCE | TestResult.Ok.INSTANCE + TestResult.Ok.INSTANCE | new TestResult.Failed() | new TestResult.Failed() + new TestResult.Failed() | TestResult.Ok.INSTANCE | new TestResult.Failed() + new TestResult.Failed() | new TestResult.Failed() | new TestResult.Failed() } } diff --git a/releasePrep.groovy b/releasePrep.groovy index d32c84f5ef..4fd25d29c9 100755 --- a/releasePrep.groovy +++ b/releasePrep.groovy @@ -107,8 +107,12 @@ ask('Tag and Push commits?: [Y]') { } ask('Publish artifacts to maven central?: [Y]') { -// executeOnShell './gradlew clean publish :pact-jvm-provider-gradle:publishPlugins -S' - executeOnShell './gradlew clean publish -S' +// executeOnShell './gradlew clean publish :pact-jvm-provider-gradle:publishPlugins -S -x :pact-publish:uploadArchives' + executeOnShell './gradlew clean publish -S -x :pact-publish:publish' +} + +ask('Publish pacts to pact-foundation.pact.dius.com.au?: [Y]') { + executeOnShell 'PACT_PUBLISH=true ./gradlew :pact-publish:test :pact-publish:pactPublish' } def nextVer = Version.valueOf(releaseVer).incrementPreReleaseVersion() diff --git a/settings.gradle b/settings.gradle index 22b0d29cd8..2b093e27dd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -39,3 +39,6 @@ project(':provider:pact-jvm-provider-scalasupport_2.12').projectDir = file('prov include 'pact-jvm-server' include 'pact-specification-test' + +include 'pact-publish' +