au.com.dius.pact.provider.reporters.JsonReporter.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pact-jvm-provider Show documentation
Show all versions of pact-jvm-provider Show documentation
Pact provider
=============
sub project of https://github.com/DiUS/pact-jvm
The pact provider is responsible for verifying that an API provider adheres to a number of pacts authored by its clients
This library provides the basic tools required to automate the process, and should be usable on its own in many instances.
Framework and build tool specific bindings will be provided in separate libraries that build on top of this core functionality.
### Provider State
Before each interaction is executed, the provider under test will have the opportunity to enter a state.
Generally the state maps to a set of fixture data for mocking out services that the provider is a consumer of (they will have their own pacts)
The pact framework will instruct the test server to enter that state by sending:
POST "${config.stateChangeUrl.url}/setup" { "state" : "${interaction.stateName}" }
### An example of running provider verification with junit
This example uses Groovy, JUnit 4 and Hamcrest matchers to run the provider verification.
As the provider service is a DropWizard application, it uses the DropwizardAppRule to startup the service before running any test.
**Warning:** It only grabs the first interaction from the pact file with the consumer, where there could be many. (This could possibly be solved with a parameterized test)
```groovy
class ReadmeExamplePactJVMProviderJUnitTest {
@ClassRule
public static final TestRule startServiceRule = new DropwizardAppRule<DropwizardConfiguration>(
TestDropwizardApplication, ResourceHelpers.resourceFilePath('dropwizard/test-config.yaml'))
private static ProviderInfo serviceProvider
private static Pact<RequestResponseInteraction> testConsumerPact
private static ConsumerInfo consumer
@BeforeClass
static void setupProvider() {
serviceProvider = new ProviderInfo('Dropwizard App')
serviceProvider.setProtocol('http')
serviceProvider.setHost('localhost')
serviceProvider.setPort(8080)
serviceProvider.setPath('/')
consumer = new ConsumerInfo()
consumer.setName('test_consumer')
consumer.setPactSource(new UrlSource(
ReadmeExamplePactJVMProviderJUnitTest.getResource('/pacts/zoo_app-animal_service.json').toString()))
testConsumerPact = DefaultPactReader.INSTANCE.loadPact(consumer.getPactSource()) as Pact<RequestResponseInteraction>
}
@Test
void runConsumerPacts() {
// grab the first interaction from the pact with consumer
Interaction interaction = testConsumerPact.interactions.get(0)
// setup the verifier
ProviderVerifier verifier = setupVerifier(interaction, serviceProvider, consumer)
// setup any provider state
// setup the client and interaction to fire against the provider
ProviderClient client = new ProviderClient(serviceProvider, new HttpClientFactory())
Map<String, Object> failures = new HashMap<>()
verifier.verifyResponseFromProvider(serviceProvider, interaction, interaction.getDescription(), failures, client)
// normally assert all good, but in this example it will fail
assertThat(failures, is(not(empty())))
verifier.displayFailures(failures)
}
private ProviderVerifier setupVerifier(Interaction interaction, ProviderInfo provider, ConsumerInfo consumer) {
ProviderVerifier verifier = new ProviderVerifier()
verifier.initialiseReporters(provider)
verifier.reportVerificationForConsumer(consumer, provider, new UrlSource('http://example.example'))
if (!interaction.getProviderStates().isEmpty()) {
for (ProviderState providerState: interaction.getProviderStates()) {
verifier.reportStateForInteraction(providerState.getName(), provider, consumer, true)
}
}
verifier.reportInteractionDescription(interaction)
return verifier
}
}
```
### An example of running provider verification with spock
This example uses groovy and spock to run the provider verification.
Again the provider service is a DropWizard application, and is using the DropwizardAppRule to startup the service.
This example runs all interactions using spocks Unroll feature
```groovy
class ReadmeExamplePactJVMProviderSpockSpec extends Specification {
@ClassRule @Shared
TestRule startServiceRule = new DropwizardAppRule<DropwizardConfiguration>(TestDropwizardApplication,
ResourceHelpers.resourceFilePath('dropwizard/test-config.yaml'))
@Shared
ProviderInfo serviceProvider
ProviderVerifier verifier
def setupSpec() {
serviceProvider = new ProviderInfo('Dropwizard App')
serviceProvider.protocol = 'http'
serviceProvider.host = 'localhost'
serviceProvider.port = 8080
serviceProvider.path = '/'
serviceProvider.hasPactWith('zoo_app') { consumer ->
consumer.pactSource = new FileSource(new File(ResourceHelpers.resourceFilePath('pacts/zoo_app-animal_service.json')))
}
}
def setup() {
verifier = new ProviderVerifier()
}
def cleanup() {
// cleanup provider state
// ie. db.truncateAllTables()
}
def cleanupSpec() {
// cleanup provider
}
@Unroll
def "Provider Pact - With Consumer #consumer"() {
expect:
!verifyConsumerPact(consumer).empty
where:
consumer << serviceProvider.consumers
}
private Map verifyConsumerPact(ConsumerInfo consumer) {
Map failures = [:]
verifier.initialiseReporters(serviceProvider)
verifier.runVerificationForConsumer(failures, serviceProvider, consumer)
if (!failures.empty) {
verifier.displayFailures(failures)
}
failures
}
}
```
The newest version!
package au.com.dius.pact.provider.reporters
import arrow.core.Either
import au.com.dius.pact.core.matchers.BodyTypeMismatch
import au.com.dius.pact.core.matchers.HeaderMismatch
import au.com.dius.pact.core.model.BasePact
import au.com.dius.pact.core.model.FileSource
import au.com.dius.pact.core.model.Interaction
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.model.PactSource
import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.UrlPactSource
import au.com.dius.pact.core.pactbroker.VerificationNotice
import au.com.dius.pact.core.support.Json
import au.com.dius.pact.core.support.hasProperty
import au.com.dius.pact.core.support.property
import au.com.dius.pact.provider.BodyComparisonResult
import au.com.dius.pact.provider.IConsumerInfo
import au.com.dius.pact.provider.IProviderInfo
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.jsonArray
import com.github.salomonbrys.kotson.jsonObject
import com.github.salomonbrys.kotson.obj
import com.github.salomonbrys.kotson.set
import com.github.salomonbrys.kotson.string
import com.github.salomonbrys.kotson.toJson
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.apache.commons.lang3.exception.ExceptionUtils
import java.io.File
import java.time.ZonedDateTime
/**
* Pact verifier reporter that generates the results of the verification in JSON format
*/
class JsonReporter(
var name: String = "json",
override var reportDir: File?,
var jsonData: JsonObject = JsonObject(),
override var ext: String = ".json",
private var providerName: String? = null
) : VerifierReporter {
constructor(name: String, reportDir: File?) : this(name, reportDir, JsonObject(), ".json", null)
override lateinit var reportFile: File
init {
if (reportDir == null) {
reportDir = File(System.getProperty("user.dir"))
}
reportFile = File(reportDir, "$name$ext")
}
override fun initialise(provider: IProviderInfo) {
providerName = provider.name
jsonData = jsonObject(
"metaData" to jsonObject(
"date" to ZonedDateTime.now().toString(),
"pactJvmVersion" to BasePact.lookupVersion(),
"reportFormat" to REPORT_FORMAT
),
"provider" to jsonObject("name" to providerName),
"execution" to jsonArray()
)
reportDir!!.mkdirs()
reportFile = File(reportDir, providerName + ext)
}
override fun finaliseReport() {
if (reportFile.exists() && reportFile.length() > 0) {
val existingContents = JsonParser().parse(reportFile.readText())
if (providerName == existingContents["provider"].obj["name"].string) {
existingContents["metaData"] = jsonData["metaData"]
existingContents["execution"].array.addAll(jsonData["execution"].array)
reportFile.writeText(existingContents.toString())
} else {
reportFile.writeText(jsonData.toString())
}
} else {
reportFile.writeText(jsonData.toString())
}
}
override fun reportVerificationForConsumer(consumer: IConsumerInfo, provider: IProviderInfo, tag: String?) {
jsonData["execution"].array.add(jsonObject(
"consumer" to jsonObject("name" to consumer.name),
"interactions" to jsonArray()
))
}
override fun verifyConsumerFromUrl(pactUrl: UrlPactSource, consumer: IConsumerInfo) {
jsonData["execution"].array.last()["consumer"].obj["source"] = jsonObject("url" to pactUrl.url)
}
override fun verifyConsumerFromFile(pactFile: PactSource, consumer: IConsumerInfo) {
jsonData["execution"].array.last()["consumer"].obj["source"] = jsonObject(
"file" to if (pactFile is FileSource<*>) pactFile.file.toString() else pactFile.description()
)
}
override fun pactLoadFailureForConsumer(consumer: IConsumerInfo, message: String) {
if (jsonData["execution"].array.size() == 0) {
jsonData["execution"].array.add(jsonObject(
"consumer" to jsonObject("name" to consumer.name),
"interactions" to jsonArray()
))
}
jsonData["execution"].array.last()["result"] = jsonObject(
"state" to "Pact Load Failure",
"message" to message
)
}
override fun warnProviderHasNoConsumers(provider: IProviderInfo) { }
override fun warnPactFileHasNoInteractions(pact: Pact) { }
override fun interactionDescription(interaction: Interaction) {
jsonData["execution"].array.last()["interactions"].array.add(jsonObject(
"interaction" to Json.toJson(interaction.toMap(PactSpecVersion.V3)),
"verification" to jsonObject("result" to "OK")
))
}
override fun stateForInteraction(
state: String,
provider: IProviderInfo,
consumer: IConsumerInfo,
isSetup: Boolean
) { }
override fun warnStateChangeIgnored(state: String, provider: IProviderInfo, consumer: IConsumerInfo) { }
override fun stateChangeRequestFailedWithException(
state: String,
provider: IProviderInfo,
consumer: IConsumerInfo,
isSetup: Boolean,
e: Exception,
printStackTrace: Boolean
) {
}
override fun stateChangeRequestFailed(state: String, provider: IProviderInfo, isSetup: Boolean, httpStatus: String) {
}
override fun warnStateChangeIgnoredDueToInvalidUrl(
state: String,
provider: IProviderInfo,
isSetup: Boolean,
stateChangeHandler: Any
) { }
override fun requestFailed(
provider: IProviderInfo,
interaction: Interaction,
interactionMessage: String,
e: Exception,
printStackTrace: Boolean
) {
jsonData["execution"].array.last()["interactions"].array.last()["verification"] = jsonObject(
"result" to FAILED,
"message" to interactionMessage,
"exception" to jsonObject(
"message" to e.message,
"stackTrace" to jsonArray(ExceptionUtils.getStackFrames(e).toList())
)
)
}
override fun returnsAResponseWhich() { }
override fun statusComparisonOk(status: Int) { }
override fun statusComparisonFailed(status: Int, comparison: Any) {
val verification = jsonData["execution"].array.last()["interactions"].array.last()["verification"]
verification["result"] = FAILED
val statusJson = jsonArray(
if (comparison.hasProperty("message")) {
comparison.property("message")?.get(comparison).toString().split('\n')
} else {
comparison.toString().split('\n')
}
)
verification["status"] = statusJson
}
override fun includesHeaders() { }
override fun headerComparisonOk(key: String, value: List) { }
override fun headerComparisonFailed(key: String, value: List, comparison: Any) {
val verification = jsonData["execution"].array.last()["interactions"].array.last()["verification"].obj
verification["result"] = FAILED
if (!verification.has("header")) {
verification["header"] = jsonObject()
}
verification["header"].obj[key] = when (comparison) {
is List<*> -> Json.toJson(comparison.map {
when (it) {
is HeaderMismatch -> it.mismatch.toJson()
else -> Json.toJson(it)
}
})
else -> Json.toJson(comparison)
}
}
override fun bodyComparisonOk() { }
override fun bodyComparisonFailed(comparison: Any) {
val verification = jsonData["execution"].array.last()["interactions"].array.last()["verification"].obj
verification["result"] = FAILED
verification["body"] = when (comparison) {
is Either.Left<*> -> Json.toJson((comparison as Either.Left).a.description())
is Either.Right<*> -> (comparison as Either.Right).b.toJson()
else -> Json.toJson(comparison)
}
}
override fun errorHasNoAnnotatedMethodsFoundForInteraction(interaction: Interaction) {
jsonData["execution"].array.last()["interactions"].array.last()["verification"] = jsonObject(
"result" to FAILED,
"cause" to jsonObject("message" to "No Annotated Methods Found For Interaction")
)
}
override fun verificationFailed(interaction: Interaction, e: Exception, printStackTrace: Boolean) {
jsonData["execution"].array.last()["interactions"].array.last()["verification"] = jsonObject(
"result" to FAILED,
"exception" to jsonObject(
"message" to e.message,
"stackTrace" to ExceptionUtils.getStackFrames(e)
)
)
}
override fun generatesAMessageWhich() { }
override fun displayFailures(failures: Map) { }
override fun metadataComparisonFailed(key: String, value: Any?, comparison: Any) {
val verification = jsonData["execution"].array.last()["interactions"].array.last()["verification"].obj
verification["result"] = FAILED
if (!verification.has("metadata")) {
verification["metadata"] = jsonObject()
}
verification["metadata"].obj[key] = comparison
}
override fun includesMetadata() { }
override fun metadataComparisonOk(key: String, value: Any?) { }
override fun metadataComparisonOk() { }
override fun reportVerificationNoticesForConsumer(
consumer: IConsumerInfo,
provider: IProviderInfo,
notices: List
) {
jsonData["execution"].array.last()["consumer"]["notices"] = jsonArray(notices.map { it.text })
}
companion object {
const val REPORT_FORMAT = "0.1.0"
const val FAILED = "failed"
}
}