From 528bcb6475ecf8c8bb4f9379d871550c2ffec340 Mon Sep 17 00:00:00 2001 From: Vasilis Charalampakis Date: Thu, 12 Mar 2020 13:39:12 +0200 Subject: [PATCH 01/10] Fix link for pact gradle plugin --- consumer/pact-jvm-consumer-groovy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consumer/pact-jvm-consumer-groovy/README.md b/consumer/pact-jvm-consumer-groovy/README.md index 136e46ef8f..ee4c341899 100644 --- a/consumer/pact-jvm-consumer-groovy/README.md +++ b/consumer/pact-jvm-consumer-groovy/README.md @@ -530,7 +530,7 @@ overwritten, set the Java system property `pact.writer.overwrite` to `true`. # Publishing your pact files to a pact broker -If you use Gradle, you can use the [pact Gradle plugin](https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-provider-gradle#publishing-pact-files-to-a-pact-broker) to publish your pact files. +If you use Gradle, you can use the [pact Gradle plugin](https://github.com/DiUS/pact-jvm/tree/master/provider/pact-jvm-provider-gradle#publishing-pact-files-to-a-pact-broker) to publish your pact files. # Pact Specification V3 From d1b192a199ffb89419bb58555f042db6d7fef949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20M=C5=82ynarski?= Date: Thu, 12 Mar 2020 22:49:18 +0100 Subject: [PATCH 02/10] #1013 - Fix to issue when in pact contract content type is not provided When the body is of type json use the application/json content type Breaking change was between pact 4.0.6 -> 4.0.7 --- .../com/dius/pact/provider/ProviderClient.kt | 8 +++++-- .../groovysupport/ProviderClientSpec.groovy | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) 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 3bc844ad41..2ed45a56e1 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 @@ -44,7 +44,7 @@ import java.util.concurrent.Callable import java.util.function.Consumer import java.util.function.Function import java.util.function.Supplier - +import au.com.dius.pact.core.model.ContentType as PactContentType interface IHttpClientFactory { fun newClient(provider: IProviderInfo): CloseableHttpClient } @@ -294,7 +294,11 @@ open class ProviderClient( } if (!method.containsHeader(CONTENT_TYPE) && request.body.isPresent()) { - method.addHeader(CONTENT_TYPE, "text/plain; charset=ISO-8859-1") + val contentType = when (request.body.contentType) { + PactContentType.UNKNOWN -> "text/plain; charset=ISO-8859-1" + else -> request.body.contentType.toString() + } + method.addHeader(CONTENT_TYPE, contentType) } } diff --git a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/groovysupport/ProviderClientSpec.groovy b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/groovysupport/ProviderClientSpec.groovy index e302b3c478..475ce7041e 100644 --- a/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/groovysupport/ProviderClientSpec.groovy +++ b/provider/pact-jvm-provider/src/test/groovy/au/com/dius/pact/provider/groovysupport/ProviderClientSpec.groovy @@ -4,6 +4,7 @@ import au.com.dius.pact.core.model.OptionalBody import au.com.dius.pact.core.model.ProviderState import au.com.dius.pact.core.model.Request import au.com.dius.pact.core.support.Json +import au.com.dius.pact.core.model.ContentType as PactContentType @SuppressWarnings('UnusedImport') import au.com.dius.pact.provider.GroovyScalaUtils$ import au.com.dius.pact.provider.IHttpClientFactory @@ -106,6 +107,28 @@ class ProviderClientSpec extends Specification { 0 * httpRequest._ } + def 'setting up headers adds an content type if none was provided and there is a body with content type'() { + given: + def headers = [ + A: ['a'], + B: ['b'], + C: ['c'] + ] + request = new Request('PUT', '/', [:], headers, OptionalBody.body('{}'.bytes, PactContentType.JSON)) + + when: + client.setupHeaders(request, httpRequest) + + then: + 1 * httpRequest.containsHeader('Content-Type') >> false + headers.each { + 1 * httpRequest.addHeader(it.key, it.value[0]) + } + 1 * httpRequest.addHeader('Content-Type', ContentType.APPLICATION_JSON.getMimeType()) + + 0 * httpRequest._ + } + def 'setting up headers does not add an TEXT content type if there is no body'() { given: def headers = [ From 045499f343736c14ebf570d330e39f36016e60a2 Mon Sep 17 00:00:00 2001 From: Guido Pio Mariotti Date: Fri, 13 Mar 2020 16:30:41 +0100 Subject: [PATCH 03/10] feat: create Kotlin friendly extension for arrays --- consumer/pact-jvm-consumer-java8/build.gradle | 8 ++-- .../pactfoundation/consumer/dsl/Extensions.kt | 9 +++- .../consumer/dsl/ExtensionsTest.kt | 44 ++++++++++++++----- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/consumer/pact-jvm-consumer-java8/build.gradle b/consumer/pact-jvm-consumer-java8/build.gradle index 5c6f7bc15d..be51452f43 100644 --- a/consumer/pact-jvm-consumer-java8/build.gradle +++ b/consumer/pact-jvm-consumer-java8/build.gradle @@ -1,12 +1,10 @@ dependencies { api project(path: ":consumer:pact-jvm-consumer", configuration: 'default') - testCompile "org.junit.jupiter:junit-jupiter-api:${project.junit5Version}" - testRuntime "org.junit.vintage:junit-vintage-engine:${project.junit5Version}" + testImplementation "org.junit.jupiter:junit-jupiter:${project.junit5Version}" testRuntime "ch.qos.logback:logback-classic:${project.logbackVersion}" - testCompile "junit:junit:${project.junitVersion}" - testCompile "org.codehaus.groovy:groovy:${project.groovyVersion}" - testCompile('org.spockframework:spock-core:2.0-M2-groovy-3.0') { + testImplementation "org.codehaus.groovy:groovy:${project.groovyVersion}" + testImplementation('org.spockframework:spock-core:2.0-M2-groovy-3.0') { exclude group: 'org.codehaus.groovy' } } diff --git a/consumer/pact-jvm-consumer-java8/src/main/kotlin/io/pactfoundation/consumer/dsl/Extensions.kt b/consumer/pact-jvm-consumer-java8/src/main/kotlin/io/pactfoundation/consumer/dsl/Extensions.kt index deccd3a9fe..8a9b2a47d3 100644 --- a/consumer/pact-jvm-consumer-java8/src/main/kotlin/io/pactfoundation/consumer/dsl/Extensions.kt +++ b/consumer/pact-jvm-consumer-java8/src/main/kotlin/io/pactfoundation/consumer/dsl/Extensions.kt @@ -23,6 +23,13 @@ fun LambdaDslObject.newObject(name: String, nestedObject: LambdaDslObject.() -> return `object`(name) { it.nestedObject() } } +/** + * Extension function to make [LambdaDslObject.array] Kotlin friendly. + */ +fun LambdaDslObject.newArray(name: String, body: LambdaDslJsonArray.() -> (Unit)): LambdaDslObject { + return array(name) { it.body() } +} + /** * Extension function to make [LambdaDslJsonArray.array] Kotlin friendly. */ @@ -35,4 +42,4 @@ fun LambdaDslJsonArray.newArray(body: LambdaDslJsonArray.() -> (Unit)): LambdaDs */ fun LambdaDslJsonArray.newObject(body: LambdaDslObject.() -> (Unit)): LambdaDslJsonArray { return `object` { it.body() } -} \ No newline at end of file +} diff --git a/consumer/pact-jvm-consumer-java8/src/test/kotlin/io/pactfoundation/consumer/dsl/ExtensionsTest.kt b/consumer/pact-jvm-consumer-java8/src/test/kotlin/io/pactfoundation/consumer/dsl/ExtensionsTest.kt index 4972a8f2b2..05d171edef 100644 --- a/consumer/pact-jvm-consumer-java8/src/test/kotlin/io/pactfoundation/consumer/dsl/ExtensionsTest.kt +++ b/consumer/pact-jvm-consumer-java8/src/test/kotlin/io/pactfoundation/consumer/dsl/ExtensionsTest.kt @@ -1,19 +1,41 @@ package io.pactfoundation.consumer.dsl +import org.hamcrest.CoreMatchers.equalTo +import org.junit.Assert.assertThat import org.junit.jupiter.api.Test class ExtensionsTest { - @Test - fun `can use LambdaDslJsonArray#newObject`() { - newJsonArray { newObject { stringType("foo") } } - } + @Test + fun `can use Kotlin DSL to create a Json Array`() { + val expectedJson = """[ + |{"key":"value"}, + |{"key_1":"value_1"} + |]""".trimMargin().replace("\n", "") - @Test - fun `can use LambdaDslObject#newObject`() { - newJsonObject { - newObject("object") { - stringType("field") - } + val actualJson = newJsonArray { + newObject { stringValue("key", "value") } + newObject { stringValue("key_1", "value_1") } + }.body.toString() + + assertThat(actualJson, equalTo(expectedJson)) + } + + @Test + fun `can use Kotlin DSL to create a Json`() { + val expectedJson = """{ + |"array":[{"key":"value"}], + |"object":{"property":"value"} + |}""".trimMargin().replace("\n", "") + + val actualJson = newJsonObject { + newArray("array") { + newObject { stringValue("key", "value") } + } + newObject("object") { + stringValue("property", "value") + } + }.body.toString() + + assertThat(actualJson, equalTo(expectedJson)) } - } } From 4215789defac5a8a71ab03d6442661febac22bad Mon Sep 17 00:00:00 2001 From: Arho Huttunen Date: Mon, 16 Mar 2020 19:45:37 +0200 Subject: [PATCH 04/10] fix: @PactBroker not reading Spring properties with JUnit 5 #1023 --- .../dius/pact/provider/junit5/PactJUnit5VerificationProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e9edd8d1b8..038baef765 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 @@ -392,11 +392,11 @@ open class PactVerificationInvocationContextProvider : TestTemplateInvocationCon logger.debug { "Verifying pacts for provider '$serviceName' and consumer '$consumerName'" } val pactSources = findPactSources(context).flatMap { - description += "\nSource: ${it.description()}" val valueResolver = getValueResolver(context) if (valueResolver != null) { it.setValueResolver(valueResolver) } + description += "\nSource: ${it.description()}" val pacts = it.load(serviceName) filterPactsByAnnotations(pacts, context.requiredTestClass).map { pact -> pact to it.pactSource } }.filter { p -> consumerName == null || p.first.consumer.name == consumerName } From 5685e85b01bdb81a9ac88be0c4c54da050ba1c14 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 21 Mar 2020 12:09:50 +1100 Subject: [PATCH 05/10] chore: Upgrade Gradle to 5.6.4 --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 2 +- gradlew.bat | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dee1ce4713..61da3fca73 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ -#Sun Jan 26 11:56:49 AEDT 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip +#Sat Mar 21 11:55:40 AEDT 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index b0d6d0ab5d..8e25e6c19d 100755 --- a/gradlew +++ b/gradlew @@ -7,7 +7,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/gradlew.bat b/gradlew.bat index 15e1ee37a7..24467a141f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -5,7 +5,7 @@ @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem -@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, From a8d6ea2efd6d21c3bebd6aac8f107f1c36713f7d Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 21 Mar 2020 12:44:24 +1100 Subject: [PATCH 06/10] chore: upgrade Kotlin to 1.3.70 --- build.gradle | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a8bf3baf1d..3bcf1856d0 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } plugins { - id 'nebula.kotlin' version '1.3.60' + id 'nebula.kotlin' version '1.3.70' id 'org.jmailen.kotlinter' version '1.26.0' id 'io.gitlab.arturbosch.detekt' version '1.0.0-RC14' id 'org.jetbrains.dokka' version '0.10.0' diff --git a/gradle.properties b/gradle.properties index dc3ba01345..1cb4ea0f81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groovyVersion=3.0.1 groovy2Version=2.5.10 -kotlinVersion=1.3.60 +kotlinVersion=1.3.70 httpBuilderVersion=1.0.4 commonsLang3Version=3.4 httpClientVersion=4.5.5 From c9ef690c44ecf7acc48f4920d46b0b1238b17fdc Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 21 Mar 2020 13:23:06 +1100 Subject: [PATCH 07/10] test: add tests for generated values with XML within JSON #1031 --- .../pact/core/model/PactReaderSpec.groovy | 22 ++++++- .../ProviderStateGeneratorSpec.groovy | 14 ++++ .../test/resources/encoded-values-pact.json | 66 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 core/model/src/test/resources/encoded-values-pact.json diff --git a/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy b/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy index ef03caad4a..1cdd5e47ef 100644 --- a/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy +++ b/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy @@ -7,8 +7,12 @@ import com.amazonaws.services.s3.model.S3Object import com.amazonaws.services.s3.model.S3ObjectInputStream import com.google.gson.JsonParser import org.apache.http.impl.client.BasicCredentialsProvider +import spock.lang.Issue import spock.lang.Specification import spock.lang.Unroll +import spock.util.environment.RestoreSystemProperties + +import static au.com.dius.pact.core.model.generators.Category.BODY @SuppressWarnings('DuplicateMapLiteral') class PactReaderSpec extends Specification { @@ -313,7 +317,7 @@ class PactReaderSpec extends Specification { @Unroll def 'determining pact spec version'() { expect: - DefaultPactReader.INSTANCE.determineSpecVersion(new JsonParser().parse(json)) == version + DefaultPactReader.INSTANCE.determineSpecVersion(JsonParser.parseString(json)) == version where: @@ -329,4 +333,20 @@ class PactReaderSpec extends Specification { } + @Issue('#1031') + def 'handle encoded values in the pact file'() { + given: + def pactUrl = PactReaderSpec.classLoader.getResource('encoded-values-pact.json') + + when: + def pact = DefaultPactReader.INSTANCE.loadPact(pactUrl) + + then: + pact instanceof RequestResponsePact + pact.interactions[0].request.body.valueAsString() == + '{"entityName":"mock-name","xml":"\\n"}' + pact.interactions[0].request.generators.categories[BODY]['$'].expression == + '{\n "entityName": "${eName}",\n "xml": "\\n"\n}' + } + } diff --git a/core/model/src/test/groovy/au/com/dius/pact/core/model/generators/ProviderStateGeneratorSpec.groovy b/core/model/src/test/groovy/au/com/dius/pact/core/model/generators/ProviderStateGeneratorSpec.groovy index 21bb36e27f..5a8cf05f3a 100644 --- a/core/model/src/test/groovy/au/com/dius/pact/core/model/generators/ProviderStateGeneratorSpec.groovy +++ b/core/model/src/test/groovy/au/com/dius/pact/core/model/generators/ProviderStateGeneratorSpec.groovy @@ -1,5 +1,6 @@ package au.com.dius.pact.core.model.generators +import spock.lang.Issue import spock.lang.Specification import spock.lang.Unroll @@ -40,4 +41,17 @@ class ProviderStateGeneratorSpec extends Specification { [a: 'A', b: 100] | '/${a}/${c}' | '/A/null' } + @Issue('#1031') + def 'handles encoded values in the expressions'() { + given: + def expression = '{\n "entityName": "${eName}",\n "xml": "\\n"\n}' + def context = [eName: 'Entity-Name'] + + when: + def result = new ProviderStateGenerator(expression).generate([providerState: context]) + + then: + result == '{\n "entityName": "Entity-Name",\n "xml": "\\n"\n}' + } + } diff --git a/core/model/src/test/resources/encoded-values-pact.json b/core/model/src/test/resources/encoded-values-pact.json new file mode 100644 index 0000000000..1db53b803f --- /dev/null +++ b/core/model/src/test/resources/encoded-values-pact.json @@ -0,0 +1,66 @@ +{ + "provider": { + "name": "Test_Provider" + }, + "consumer": { + "name": "Test_Consumer" + }, + "interactions": [ + { + "description": "A request to create new entity", + "request": { + "method": "POST", + "path": "/test", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "entityName": "mock-name", + "xml": "\u003c?xml version\u003d\"1.0\" encoding\u003d\"UTF-8\"?\u003e\n" + }, + "generators": { + "body": { + "$": { + "type": "ProviderState", + "expression": "{\n \"entityName\": \"${eName}\",\n \"xml\": \"\u003c?xml version\u003d\\\"1.0\\\" encoding\u003d\\\"UTF-8\\\"?\u003e\\n\"\n}" + } + } + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json;charset\u003dUTF-8" + }, + "body": { + "workflowDefinitionName": "mock-name" + }, + "matchingRules": { + "body": { + "$.workflowDefinitionName": { + "matchers": [ + { + "match": "type" + } + ], + "combine": "AND" + } + } + } + }, + "providerStates": [ + { + "name": "A entity name not exists" + } + ] + } + ], + "metadata": { + "pactSpecification": { + "version": "3.0.0" + }, + "pact-jvm": { + "version": "3.6.11" + } + } +} From 9332e4f4b7b86f4aa73503b7e8ff22b6ef8803fc Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 21 Mar 2020 13:34:07 +1100 Subject: [PATCH 08/10] fix: codenarc --- .../groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy b/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy index 1cdd5e47ef..ac545235ed 100644 --- a/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy +++ b/core/model/src/test/groovy/au/com/dius/pact/core/model/PactReaderSpec.groovy @@ -10,7 +10,6 @@ import org.apache.http.impl.client.BasicCredentialsProvider import spock.lang.Issue import spock.lang.Specification import spock.lang.Unroll -import spock.util.environment.RestoreSystemProperties import static au.com.dius.pact.core.model.generators.Category.BODY @@ -334,6 +333,7 @@ class PactReaderSpec extends Specification { } @Issue('#1031') + @SuppressWarnings('GStringExpressionWithinString') def 'handle encoded values in the pact file'() { given: def pactUrl = PactReaderSpec.classLoader.getResource('encoded-values-pact.json') From 8bf077e6ad0ebe02a3ba0e51ba07ce624769fc3f Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 21 Mar 2020 14:48:32 +1100 Subject: [PATCH 09/10] fix: corrected junit 5 tests to support injecting the mock server in a before callback --- .../pact-jvm-consumer-junit5/build.gradle | 1 + .../consumer/junit5/PactConsumerTestExt.kt | 243 +++++++++--------- .../junit5/PactConsumerTestExtTest.groovy | 43 +++- .../pact/consumer/junit5/ArticlesTest.java | 7 + 4 files changed, 167 insertions(+), 127 deletions(-) diff --git a/consumer/pact-jvm-consumer-junit5/build.gradle b/consumer/pact-jvm-consumer-junit5/build.gradle index f38177ebc2..e34f272dfe 100644 --- a/consumer/pact-jvm-consumer-junit5/build.gradle +++ b/consumer/pact-jvm-consumer-junit5/build.gradle @@ -11,6 +11,7 @@ dependencies { testCompile "org.codehaus.groovy:groovy-xml:${project.groovyVersion}" testCompile 'org.apache.commons:commons-io:1.3.2' testRuntime "org.junit.vintage:junit-vintage-engine:${project.junit5Version}" + testRuntime "org.junit.jupiter:junit-jupiter-engine:${project.junit5Version}" testCompile project(path: ":consumer:pact-jvm-consumer-java8", configuration: 'default') testCompile 'org.hamcrest:hamcrest:2.1' testCompile('org.spockframework:spock-core:2.0-M2-groovy-3.0') { diff --git a/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt b/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt index d9659b4aa3..c16ba6c82d 100644 --- a/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt +++ b/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt @@ -135,169 +135,178 @@ class JUnit5MockServerSupport(private val baseMockServer: BaseMockServer) : Mock class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCallback, ParameterResolver, AfterTestExecutionCallback, AfterAllCallback { override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { - val store = extensionContext.getStore(ExtensionContext.Namespace.create("pact-jvm")) - if (store["providerInfo"] != null) { - val providerInfo = store["providerInfo"] as ProviderInfo - val type = parameterContext.parameter.type - return when (providerInfo.providerType) { - ProviderType.ASYNCH -> when { - type.isAssignableFrom(List::class.java) -> true - type.isAssignableFrom(MessagePact::class.java) -> true - else -> false - } - else -> when { - type.isAssignableFrom(MockServer::class.java) -> true - type.isAssignableFrom(RequestResponsePact::class.java) -> true - else -> false - } + val providerInfo = lookupProviderInfo(extensionContext).first + val type = parameterContext.parameter.type + return when (providerInfo.providerType) { + ProviderType.ASYNCH -> when { + type.isAssignableFrom(List::class.java) -> true + type.isAssignableFrom(MessagePact::class.java) -> true + else -> false + } + else -> when { + type.isAssignableFrom(MockServer::class.java) -> true + type.isAssignableFrom(RequestResponsePact::class.java) -> true + else -> false } - } else { - return false } } override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { - val store = extensionContext.getStore(ExtensionContext.Namespace.create("pact-jvm")) - if (store["providerInfo"] != null) { - val providerInfo = store["providerInfo"] as ProviderInfo - val type = parameterContext.parameter.type - return when (providerInfo.providerType) { - ProviderType.ASYNCH -> { - val pact = store["pact"] as MessagePact - when { - type.isAssignableFrom(List::class.java) -> pact.messages - type.isAssignableFrom(MessagePact::class.java) -> pact - else -> throw UnsupportedOperationException("Could not inject parameter $type into test method") - } + val providerInfo = lookupProviderInfo(extensionContext) + val store = extensionContext.getStore(NAMESPACE) + val type = parameterContext.parameter.type + return when (providerInfo.first.providerType) { + ProviderType.ASYNCH -> { + val pact = lookupPact(providerInfo.first, providerInfo.second, extensionContext) as MessagePact + when { + type.isAssignableFrom(List::class.java) -> pact.messages + type.isAssignableFrom(MessagePact::class.java) -> pact + else -> throw UnsupportedOperationException("Could not inject parameter $type into test method") } - else -> { - val pact = store["pact"] as RequestResponsePact - when { - type.isAssignableFrom(MockServer::class.java) -> store["mockServer"] - type.isAssignableFrom(RequestResponsePact::class.java) -> pact - else -> throw UnsupportedOperationException("Could not inject parameter $type into test method") - } + } + else -> { + when { + type.isAssignableFrom(MockServer::class.java) -> setupMockServer(providerInfo.first, providerInfo.second, extensionContext)!! + type.isAssignableFrom(RequestResponsePact::class.java) -> store["pact"] as RequestResponsePact + else -> throw UnsupportedOperationException("Could not inject parameter $type into test method") } } - } else { - return false } } override fun beforeAll(context: ExtensionContext) { - val store = context.getStore(ExtensionContext.Namespace.create("pact-jvm")) + val store = context.getStore(NAMESPACE) store.put("executedFragments", mutableListOf()) } override fun beforeTestExecution(context: ExtensionContext) { val (providerInfo, pactMethod) = lookupProviderInfo(context) - logger.debug { "providerInfo = $providerInfo" } - val store = context.getStore(ExtensionContext.Namespace.create("pact-jvm")) - val executedFragments = store["executedFragments"] as MutableList - val pact = lookupPact(providerInfo, pactMethod, context, executedFragments) - store.put("pact", pact) - store.put("providerInfo", providerInfo) + setupMockServer(providerInfo, pactMethod, context) + } - if (providerInfo.providerType != ProviderType.ASYNCH) { + private fun setupMockServer(providerInfo: ProviderInfo, pactMethod: String, context: ExtensionContext): MockServer? { + val store = context.getStore(NAMESPACE) + return if (providerInfo.providerType != ProviderType.ASYNCH && store["mockServer"] == null) { val config = providerInfo.mockServerConfig() store.put("mockServerConfig", config) - val mockServer = mockServer(pact as RequestResponsePact, config) as BaseMockServer + val mockServer = mockServer(lookupPact(providerInfo, pactMethod, context) as RequestResponsePact, config) as BaseMockServer mockServer.start() mockServer.waitForServer() store.put("mockServer", JUnit5MockServerSupport(mockServer)) + mockServer + } else { + store["mockServer"] as MockServer? } } fun lookupProviderInfo(context: ExtensionContext): Pair { - val methodAnnotation = if (AnnotationSupport.isAnnotated(context.requiredTestMethod, PactTestFor::class.java)) { - logger.debug { "Found @PactTestFor annotation on test method" } - val annotation = AnnotationSupport.findAnnotation(context.requiredTestMethod, PactTestFor::class.java).get() - ProviderInfo.fromAnnotation(annotation) to annotation.pactMethod + val store = context.getStore(NAMESPACE) + return if (store["providerInfo"] != null) { + (store["providerInfo"] as ProviderInfo) to store["pactMethod"].toString() } else { - null - } + val methodAnnotation = if (AnnotationSupport.isAnnotated(context.requiredTestMethod, PactTestFor::class.java)) { + logger.debug { "Found @PactTestFor annotation on test method" } + val annotation = AnnotationSupport.findAnnotation(context.requiredTestMethod, PactTestFor::class.java).get() + ProviderInfo.fromAnnotation(annotation) to annotation.pactMethod + } else { + null + } - val classAnnotation = if (AnnotationSupport.isAnnotated(context.requiredTestClass, PactTestFor::class.java)) { - logger.debug { "Found @PactTestFor annotation on test class" } - val annotation = AnnotationSupport.findAnnotation(context.requiredTestClass, PactTestFor::class.java).get() - ProviderInfo.fromAnnotation(annotation) to annotation.pactMethod - } else { - null - } + val classAnnotation = if (AnnotationSupport.isAnnotated(context.requiredTestClass, PactTestFor::class.java)) { + logger.debug { "Found @PactTestFor annotation on test class" } + val annotation = AnnotationSupport.findAnnotation(context.requiredTestClass, PactTestFor::class.java).get() + ProviderInfo.fromAnnotation(annotation) to annotation.pactMethod + } else { + null + } - return when { - classAnnotation != null && methodAnnotation != null -> Pair(methodAnnotation.first.merge(classAnnotation.first), - if (methodAnnotation.second.isNotEmpty()) methodAnnotation.second else classAnnotation.second) - classAnnotation != null -> classAnnotation - methodAnnotation != null -> methodAnnotation - else -> { - logger.debug { "No @PactTestFor annotation found on test class, using defaults" } - ProviderInfo() to "" + val providerInfo = when { + classAnnotation != null && methodAnnotation != null -> Pair(methodAnnotation.first.merge(classAnnotation.first), + if (methodAnnotation.second.isNotEmpty()) methodAnnotation.second else classAnnotation.second) + classAnnotation != null -> classAnnotation + methodAnnotation != null -> methodAnnotation + else -> { + logger.debug { "No @PactTestFor annotation found on test class, using defaults" } + ProviderInfo() to "" + } } + + store.put("providerInfo", providerInfo.first) + store.put("pactMethod", providerInfo.second) + + providerInfo } } fun lookupPact( providerInfo: ProviderInfo, pactMethod: String, - context: ExtensionContext, - executedFragments: MutableList + context: ExtensionContext ): BasePact { - val providerName = if (providerInfo.providerName.isEmpty()) "default" else providerInfo.providerName - val methods = AnnotationSupport.findAnnotatedMethods(context.requiredTestClass, Pact::class.java, - HierarchyTraversalMode.TOP_DOWN) - - val method = when { - pactMethod.isNotEmpty() -> { - logger.debug { "Looking for @Pact method named '$pactMethod' for provider '$providerName'" } - methods.firstOrNull { it.name == pactMethod } - } - providerInfo.providerName.isEmpty() -> { - logger.debug { "Looking for first @Pact method" } - methods.firstOrNull() - } - else -> { - logger.debug { "Looking for first @Pact method for provider '$providerName'" } - methods.firstOrNull { - val annotationProviderName = AnnotationSupport.findAnnotation(it, Pact::class.java).get().provider - annotationProviderName.isEmpty() || annotationProviderName == providerInfo.providerName + val store = context.getStore(NAMESPACE) + if (store["pact"] == null) { + val providerName = if (providerInfo.providerName.isEmpty()) "default" else providerInfo.providerName + val methods = AnnotationSupport.findAnnotatedMethods(context.requiredTestClass, Pact::class.java, + HierarchyTraversalMode.TOP_DOWN) + + val method = when { + pactMethod.isNotEmpty() -> { + logger.debug { "Looking for @Pact method named '$pactMethod' for provider '$providerName'" } + methods.firstOrNull { it.name == pactMethod } + } + providerInfo.providerName.isEmpty() -> { + logger.debug { "Looking for first @Pact method" } + methods.firstOrNull() + } + else -> { + logger.debug { "Looking for first @Pact method for provider '$providerName'" } + methods.firstOrNull { + val annotationProviderName = AnnotationSupport.findAnnotation(it, Pact::class.java).get().provider + annotationProviderName.isEmpty() || annotationProviderName == providerInfo.providerName + } } } - } - val providerType = providerInfo.providerType ?: ProviderType.SYNCH - if (method == null) { - throw UnsupportedOperationException("No method annotated with @Pact was found on test class " + - context.requiredTestClass.simpleName + " for provider '${providerInfo.providerName}'") - } else if (providerType == ProviderType.SYNCH && !JUnitTestSupport.conformsToSignature(method)) { - throw UnsupportedOperationException("Method ${method.name} does not conform to required method signature " + - "'public RequestResponsePact xxx(PactDslWithProvider builder)'") - } else if (providerType == ProviderType.ASYNCH && !JUnitTestSupport.conformsToMessagePactSignature(method)) { - throw UnsupportedOperationException("Method ${method.name} does not conform to required method signature " + - "'public MessagePact xxx(MessagePactBuilder builder)'") - } + val providerType = providerInfo.providerType ?: ProviderType.SYNCH + if (method == null) { + throw UnsupportedOperationException("No method annotated with @Pact was found on test class " + + context.requiredTestClass.simpleName + " for provider '${providerInfo.providerName}'") + } else if (providerType == ProviderType.SYNCH && !JUnitTestSupport.conformsToSignature(method)) { + throw UnsupportedOperationException("Method ${method.name} does not conform to required method signature " + + "'public RequestResponsePact xxx(PactDslWithProvider builder)'") + } else if (providerType == ProviderType.ASYNCH && !JUnitTestSupport.conformsToMessagePactSignature(method)) { + throw UnsupportedOperationException("Method ${method.name} does not conform to required method signature " + + "'public MessagePact xxx(MessagePactBuilder builder)'") + } - val pactAnnotation = AnnotationSupport.findAnnotation(method, Pact::class.java).get() - logger.debug { "Invoking method '${method.name}' to get Pact for the test " + - "'${context.testMethod.map { it.name }.orElse("unknown")}'" } - - val provider = parseExpression(pactAnnotation.provider).toString() - val providerNameToUse = if (provider.isNotEmpty()) provider else providerName - val pact = when (providerType) { - ProviderType.SYNCH, ProviderType.UNSPECIFIED -> ReflectionSupport.invokeMethod(method, context.requiredTestInstance, - ConsumerPactBuilder.consumer(pactAnnotation.consumer).hasPactWith(providerNameToUse)) as BasePact<*> - ProviderType.ASYNCH -> ReflectionSupport.invokeMethod(method, context.requiredTestInstance, - MessagePactBuilder.consumer(pactAnnotation.consumer).hasPactWith(providerNameToUse)) as BasePact<*> + val pactAnnotation = AnnotationSupport.findAnnotation(method, Pact::class.java).get() + logger.debug { + "Invoking method '${method.name}' to get Pact for the test " + + "'${context.testMethod.map { it.name }.orElse("unknown")}'" + } + + val provider = parseExpression(pactAnnotation.provider).toString() + val providerNameToUse = if (provider.isNotEmpty()) provider else providerName + val pact = when (providerType) { + ProviderType.SYNCH, ProviderType.UNSPECIFIED -> ReflectionSupport.invokeMethod(method, context.requiredTestInstance, + ConsumerPactBuilder.consumer(pactAnnotation.consumer).hasPactWith(providerNameToUse)) as BasePact<*> + ProviderType.ASYNCH -> ReflectionSupport.invokeMethod(method, context.requiredTestInstance, + MessagePactBuilder.consumer(pactAnnotation.consumer).hasPactWith(providerNameToUse)) as BasePact<*> + } + val executedFragments = store["executedFragments"] as MutableList + executedFragments.add(method) + store.put("pact", pact) + return pact + } else { + return store["pact"] as BasePact } - executedFragments.add(method) - return pact } override fun afterTestExecution(context: ExtensionContext) { if (!context.executionException.isPresent) { - val store = context.getStore(ExtensionContext.Namespace.create("pact-jvm")) + val store = context.getStore(NAMESPACE) val providerInfo = store["providerInfo"] as ProviderInfo val pactDirectory = lookupPactDirectory(context) if (providerInfo.providerType != ProviderType.ASYNCH) { @@ -338,7 +347,7 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal override fun afterAll(context: ExtensionContext) { if (!context.executionException.isPresent) { - val store = context.getStore(ExtensionContext.Namespace.create("pact-jvm")) + val store = context.getStore(NAMESPACE) val executedFragments = store["executedFragments"] as MutableList val methods = AnnotationSupport.findAnnotatedMethods(context.requiredTestClass, Pact::class.java, HierarchyTraversalMode.TOP_DOWN) @@ -355,5 +364,7 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal } } - companion object : KLogging() + companion object : KLogging() { + val NAMESPACE = ExtensionContext.Namespace.create("pact-jvm") + } } diff --git a/consumer/pact-jvm-consumer-junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy b/consumer/pact-jvm-consumer-junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy index 51d9ebd4e2..39c5d8d010 100644 --- a/consumer/pact-jvm-consumer-junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy +++ b/consumer/pact-jvm-consumer-junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy @@ -7,6 +7,7 @@ import au.com.dius.pact.core.model.PactSpecVersion import au.com.dius.pact.core.model.Provider import au.com.dius.pact.core.model.RequestResponsePact import org.hamcrest.Matchers +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtensionContext @@ -20,6 +21,20 @@ class PactConsumerTestExtTest { private providerInfo = new ProviderInfo() private pact = new RequestResponsePact(new Provider('junit5_provider'), new Consumer('junit5_consumer'), []) + private ExtensionContext.Store mockStore + + @BeforeEach + void setup() { + mockStore = [ + 'get': { param -> + switch (param) { + case 'executedFragments': []; break + default: null + } + }, + 'put': { param, value -> } + ] as ExtensionContext.Store + } class TestClassInvalidSignature { @Pact(provider = 'junit5_provider', consumer = 'junit5_consumer') @@ -80,7 +95,7 @@ class PactConsumerTestExtTest { void test1() { assertThrows(UnsupportedOperationException) { def context = ['getTestClass': { Optional.of(PactConsumerTestExtTest) } ] as ExtensionContext - subject.lookupPact(providerInfo, '', context, []) + subject.lookupPact(providerInfo, '', context) } } @@ -89,7 +104,7 @@ class PactConsumerTestExtTest { void test2() { assertThrows(UnsupportedOperationException) { def context = ['getTestClass': { Optional.of(PactConsumerTestExtTest) } ] as ExtensionContext - subject.lookupPact(providerInfo, 'test', context, []) + subject.lookupPact(providerInfo, 'test', context) } } @@ -98,7 +113,7 @@ class PactConsumerTestExtTest { void test3() { assertThrows(UnsupportedOperationException) { def context = ['getTestClass': { Optional.of(TestClassInvalidSignature) } ] as ExtensionContext - subject.lookupPact(providerInfo, 'pactMethod', context, []) + subject.lookupPact(providerInfo, 'pactMethod', context) } } @@ -107,7 +122,7 @@ class PactConsumerTestExtTest { void test4() { assertThrows(UnsupportedOperationException) { def context = ['getTestClass': { Optional.of(TestClass) } ] as ExtensionContext - subject.lookupPact(providerInfo, 'pactMethod', context, []) + subject.lookupPact(providerInfo, 'pactMethod', context) } } @@ -117,10 +132,11 @@ class PactConsumerTestExtTest { def context = [ 'getTestClass': { Optional.of(TestClass) }, 'getTestInstance': { Optional.of(new TestClass()) }, - 'getTestMethod': { Optional.empty() } + 'getTestMethod': { Optional.empty() }, + 'getStore': { mockStore } ] as ExtensionContext def pact = subject.lookupPact(new ProviderInfo('junit5_provider', 'localhost', '8080', - PactSpecVersion.V3, ProviderType.SYNCH), 'pactMethod', context, []) + PactSpecVersion.V3, ProviderType.SYNCH), 'pactMethod', context) assertThat(pact, Matchers.is(this.pact)) } @@ -131,7 +147,8 @@ class PactConsumerTestExtTest { def context = [ 'getTestClass': { Optional.of(TestClass) }, 'getTestInstance': { Optional.of(instance) }, - 'getTestMethod': { Optional.of(TestClass.methods.find { it.name == 'pactMethod' }) } + 'getTestMethod': { Optional.of(TestClass.methods.find { it.name == 'pactMethod' }) }, + 'getStore': { mockStore } ] as ExtensionContext def providerInfo = subject.lookupProviderInfo(context) assertThat(providerInfo.first.providerName, Matchers.is('')) @@ -148,7 +165,8 @@ class PactConsumerTestExtTest { def context = [ 'getTestClass': { Optional.of(TestClassWithClassLevelAnnotation) }, 'getTestInstance': { Optional.of(instance) }, - 'getTestMethod': { Optional.of(TestClassWithClassLevelAnnotation.methods.find { it.name == 'pactMethod' }) } + 'getTestMethod': { Optional.of(TestClassWithClassLevelAnnotation.methods.find { it.name == 'pactMethod' }) }, + 'getStore': { mockStore } ] as ExtensionContext def providerInfo = subject.lookupProviderInfo(context) assertThat(providerInfo.first.providerName, Matchers.is('TestClassWithClassLevelAnnotation')) @@ -165,7 +183,8 @@ class PactConsumerTestExtTest { def context = [ 'getTestClass': { Optional.of(TestClassWithMethodLevelAnnotation) }, 'getTestInstance': { Optional.of(instance) }, - 'getTestMethod': { Optional.of(TestClassWithMethodLevelAnnotation.methods.find { it.name == 'pactMethod' }) } + 'getTestMethod': { Optional.of(TestClassWithMethodLevelAnnotation.methods.find { it.name == 'pactMethod' }) }, + 'getStore': { mockStore } ] as ExtensionContext def providerInfo = subject.lookupProviderInfo(context) assertThat(providerInfo.first.providerName, Matchers.is('TestClassWithMethodLevelAnnotation')) @@ -184,7 +203,8 @@ class PactConsumerTestExtTest { 'getTestInstance': { Optional.of(instance) }, 'getTestMethod': { Optional.of(TestClassWithMethodAndClassLevelAnnotation.methods.find { it.name == 'pactMethod' }) - } + }, + 'getStore': { mockStore } ] as ExtensionContext def providerInfo = subject.lookupProviderInfo(context) assertThat(providerInfo.first.providerName, Matchers.is('TestClassWithMethodAndClassLevelAnnotation')) @@ -203,7 +223,8 @@ class PactConsumerTestExtTest { 'getTestInstance': { Optional.of(instance) }, 'getTestMethod': { Optional.of(TestClassWithMethodAndClassLevelAnnotation2.methods.find { it.name == 'pactMethod' }) - } + }, + 'getStore': { mockStore } ] as ExtensionContext def providerInfo = subject.lookupProviderInfo(context) assertThat(providerInfo.first.pactVersion, Matchers.is(PactSpecVersion.V3)) diff --git a/consumer/pact-jvm-consumer-junit5/src/test/java/au/com/dius/pact/consumer/junit5/ArticlesTest.java b/consumer/pact-jvm-consumer-junit5/src/test/java/au/com/dius/pact/consumer/junit5/ArticlesTest.java index fee1970f66..041957bcb1 100644 --- a/consumer/pact-jvm-consumer-junit5/src/test/java/au/com/dius/pact/consumer/junit5/ArticlesTest.java +++ b/consumer/pact-jvm-consumer-junit5/src/test/java/au/com/dius/pact/consumer/junit5/ArticlesTest.java @@ -9,6 +9,7 @@ import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.fluent.Request; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -19,6 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; @ExtendWith(PactConsumerTestExt.class) @PactTestFor(providerName = "ArticlesProvider", port = "1234") @@ -27,6 +29,11 @@ public class ArticlesTest { "Content-Type", "application/json" }); + @BeforeEach + public void setUp(MockServer mockServer) { + assertThat(mockServer, is(notNullValue())); + } + @Pact(consumer = "ArticlesConsumer") public RequestResponsePact articles(PactDslWithProvider builder) { return builder From d38fda12284b0e2b7779ada715c1f16706c39fd1 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 21 Mar 2020 15:17:40 +1100 Subject: [PATCH 10/10] fix: typo in exception message --- .../au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt b/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt index c16ba6c82d..e653b1a017 100644 --- a/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt +++ b/consumer/pact-jvm-consumer-junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt @@ -358,7 +358,7 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal if (nonExecutedMethods.isNotEmpty()) { throw AssertionError( "The following methods annotated with @Pact were not executed during the test: $nonExecutedMethods" + - "\nIf these are currently a work in progress, and a @Disabled annotation to the method\n") + "\nIf these are currently a work in progress, add a @Disabled annotation to the method\n") } } }