Created
October 16, 2019 15:05
-
-
Save siwater/4144ce5d8aa1e5a157dd39af745a43bb to your computer and use it in GitHub Desktop.
This code does not lose a Pact in PactRunner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The attached file shows the change I made to filtering in PactRunner.kt (replace the call to filterPacts with one to filterPactsByAnnotations | |
at line 79) | |
Here is a sample log output: | |
2019-10-16 16:01:36.336 DEBUG 3464 --- [ main] a.c.d.p.p.junit.loader.PactBrokerLoader : Loading pacts from pact broker for provider cas-app-actions and tag latest | |
2019-10-16 16:01:36.369 DEBUG 3464 --- [ main] a.c.dius.pact.core.pactbroker.HalClient : Fetching: / | |
2019-10-16 16:01:38.127 DEBUG 3464 --- [ main] a.c.dius.pact.core.pactbroker.HalClient : Fetching: https://cas-pact-broker.eng.citrite.net/pacts/provider/cas-app-actions/latest | |
2019-10-16 16:01:38.205 DEBUG 3464 --- [ main] a.c.dius.pact.core.pactbroker.HalClient : Fetching: https://cas-pact-broker.eng.citrite.net/pacts/provider/cas-app-actions/consumer/sf-action-adapter/version/1.0.0 | |
2019-10-16 16:01:38.370 DEBUG 3464 --- [ main] a.c.dius.pact.provider.junit.PactRunner : Loaded 1 pacts in java.util.ArrayList | |
Verifying a pact between sf-action-adapter and cas-app-actions | |
Given Received the message to trigger disable share action | |
Disable share action payload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package au.com.dius.pact.provider.junit | |
import au.com.dius.pact.core.model.Interaction | |
import au.com.dius.pact.core.model.Pact | |
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver | |
import au.com.dius.pact.provider.junit.JUnitProviderTestSupport.filterPactsByAnnotations | |
import au.com.dius.pact.provider.junit.loader.NoPactsFoundException | |
import au.com.dius.pact.provider.junit.loader.PactBroker | |
import au.com.dius.pact.provider.junit.loader.PactFolder | |
import au.com.dius.pact.provider.junit.loader.PactLoader | |
import au.com.dius.pact.provider.junit.loader.PactSource | |
import au.com.dius.pact.provider.junit.target.HttpTarget | |
import au.com.dius.pact.provider.junit.target.Target | |
import au.com.dius.pact.provider.junit.target.TestTarget | |
import com.google.gson.JsonSyntaxException | |
import mu.KLogging | |
import org.junit.Ignore | |
import org.junit.runner.notification.RunNotifier | |
import org.junit.runners.ParentRunner | |
import org.junit.runners.model.InitializationError | |
import org.junit.runners.model.TestClass | |
import java.io.IOException | |
import kotlin.reflect.full.createInstance | |
import kotlin.reflect.full.findAnnotation | |
/** | |
* JUnit Runner runs pacts against provider | |
* To set up name of tested provider use [Provider] annotation | |
* To point on pact's source use [PactBroker], [PactFolder] or [PactSource] annotations | |
* | |
* | |
* To point provider for testing use combination of [Target] interface and [TestTarget] annotation | |
* There is out-of-the-box implementation of [Target]: | |
* [HttpTarget] that will play interaction from pacts as http request and check http responses | |
* | |
* | |
* Runner supports: | |
* - [org.junit.BeforeClass], [org.junit.AfterClass] and [org.junit.ClassRule] annotations, | |
* that will be run once - before/after whole contract test suite | |
* | |
* | |
* - [org.junit.Before], [org.junit.After] and [org.junit.Rule] annotations, | |
* that will be run before/after each test of interaction | |
* **WARNING:** please note, that only [org.junit.rules.TestRule] is possible to use with this runner, | |
* i.e. [org.junit.rules.MethodRule] **IS NOT supported** | |
* | |
* | |
* - [State] - before each interaction that require state change, | |
* all methods annotated by [State] with appropriate state listed will be invoked | |
*/ | |
open class PactRunner<I>(clazz: Class<*>) : ParentRunner<InteractionRunner<I>>(clazz) where I : Interaction { | |
private val child = mutableListOf<InteractionRunner<I>>() | |
private var valueResolver = SystemPropertyResolver() | |
init { | |
if (clazz.getAnnotation(Ignore::class.java) != null) { | |
logger.info("Ignore annotation detected, exiting") | |
} else { | |
val providerInfo = clazz.getAnnotation(Provider::class.java) ?: throw InitializationError( | |
"Provider name should be specified by using ${Provider::class.java.name} annotation") | |
val serviceName = providerInfo.value | |
val consumerInfo = clazz.getAnnotation(Consumer::class.java) | |
val consumerName = consumerInfo?.value | |
val testClass = TestClass(clazz) | |
val ignoreNoPactsToVerify = clazz.getAnnotation(IgnoreNoPactsToVerify::class.java) | |
val ignoreIoErrors = try { | |
valueResolver.resolveValue(ignoreNoPactsToVerify?.ignoreIoErrors) | |
} catch (e: RuntimeException) { | |
logger.debug(e) { "Failed to resolve property value" } | |
ignoreNoPactsToVerify?.ignoreIoErrors | |
} ?: "false" | |
val pactLoader = getPactSource(testClass) | |
val pacts = try { | |
filterPactsByAnnotations(pactLoader.load(serviceName) | |
.filter { p -> consumerName == null || p.consumer.name == consumerName } as List<Pact<I>>,testClass.javaClass) | |
} catch (e: IOException) { | |
if (ignoreIoErrors == "true") { | |
logger.warn { "\n" + WARNING_ON_IGNORED_IOERROR.trimIndent() } | |
logger.debug(e) { "Failed to load pact files" } | |
emptyList<Pact<I>>() | |
} else { | |
throw InitializationError(e) | |
} | |
} catch (e: JsonSyntaxException) { | |
if (ignoreIoErrors == "true") { | |
logger.warn { "\n" + WARNING_ON_IGNORED_IOERROR.trimIndent() } | |
logger.debug(e) { "Failed to load pact files" } | |
emptyList<Pact<I>>() | |
} else { | |
throw InitializationError(e) | |
} | |
} catch (e: NoPactsFoundException) { | |
logger.debug(e) { "No pacts found" } | |
emptyList<Pact<I>>() | |
} | |
logger.debug("Loaded " + pacts.size + " pacts in ${pacts::class.qualifiedName}") | |
if (pacts.isEmpty()) { | |
if (ignoreNoPactsToVerify != null) { | |
logger.warn { "Did not find any pact files for provider ${providerInfo.value}" } | |
} else { | |
throw InitializationError("Did not find any pact files for provider ${providerInfo.value}") | |
} | |
} | |
setupInteractionRunners(testClass, pacts, pactLoader) | |
} | |
} | |
protected open fun setupInteractionRunners(testClass: TestClass, pacts: List<Pact<I>>, pactLoader: PactLoader) { | |
for (pact in pacts) { | |
this.child.add(newInteractionRunner(testClass, pact, pactLoader.pactSource)) | |
} | |
} | |
protected open fun newInteractionRunner( | |
testClass: TestClass, | |
pact: Pact<I>, | |
pactSource: au.com.dius.pact.core.model.PactSource | |
): InteractionRunner<I> { | |
return InteractionRunner(testClass, pact, pactSource) | |
} | |
protected open fun filterPacts(pacts: List<Pact<I>>): List<Pact<I>> { | |
logger.debug("filterPacts input: ${pacts.size}") | |
val result = filterPactsByAnnotations(pacts, testClass.javaClass) | |
logger.debug("filterPacts output: ${result.size}") | |
return result | |
} | |
override fun getChildren() = child | |
override fun describeChild(child: InteractionRunner<I>) = child.description | |
override fun runChild(interaction: InteractionRunner<I>, notifier: RunNotifier) { | |
interaction.run(notifier) | |
} | |
protected open fun getPactSource(clazz: TestClass): PactLoader { | |
val pactSource = clazz.getAnnotation(PactSource::class.java) | |
val pactLoaders = clazz.annotations | |
.filter { annotation -> annotation.annotationClass.findAnnotation<PactSource>() != null } | |
if ((if (pactSource == null) 0 else 1) + pactLoaders.size != 1) { | |
throw InitializationError("Exactly one pact source should be set") | |
} | |
try { | |
if (pactSource != null) { | |
val pactLoaderClass = pactSource.value | |
return try { | |
// Checks if there is a constructor with one argument of type Class. | |
val constructorWithClass = pactLoaderClass.java.getDeclaredConstructor(Class::class.java) | |
if (constructorWithClass != null) { | |
constructorWithClass.isAccessible = true | |
constructorWithClass.newInstance(clazz.javaClass) | |
} else { | |
pactLoaderClass.createInstance() | |
} | |
} catch (e: NoSuchMethodException) { | |
logger.error(e) { e.message } | |
pactLoaderClass.createInstance() | |
} | |
} else { | |
val annotation = pactLoaders.first() | |
return annotation.annotationClass.findAnnotation<PactSource>()!!.value.java | |
.getConstructor(annotation.annotationClass.java).newInstance(annotation) | |
} | |
} catch (e: ReflectiveOperationException) { | |
logger.error(e) { "Error while creating pact source" } | |
throw InitializationError(e) | |
} | |
} | |
companion object : KLogging() { | |
const val WARNING_ON_IGNORED_IOERROR = """ | |
--------------------------------------------------------------------------- | |
| WARNING! Ignoring IO Exception received when loading Pact files as | | |
| WARNING! the @IgnoreNoPactsToVerify annotation is present and | | |
| WARNING! ignoreIoErrors is set to true. Make sure this is not happening | | |
| WARNING! on your CI server, as this could result in your build passing | | |
| WARNING! with no providers having been verified due to a configuration | | |
| WARNING! error. | | |
-------------------------------------------------------------------------""" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment