au.com.dius.pact.provider.ResponseComparison.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pact-jvm-provider_2.10 Show documentation
Show all versions of pact-jvm-provider_2.10 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.
### Running Pacts
Main takes 2 arguments:
The first is the root folder of your pact files
(all .json files in root and subfolders are assumed to be pacts)
The second is the location of your pact config json file.
### Pact config
The pact config is a simple mapping of provider names to endpoint url's
paths will be appended to endpoint url's when interactions are attempted
for an example see: https://github.com/DiUS/pact-jvm/blob/master/pact-jvm-provider/src/test/resources/pact-config.json
### 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 java, junit 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)
```java
public class PactJVMProviderJUnitTest {
@ClassRule
public static TestRule startServiceRule = new DropwizardAppRule<DropwizardAppConfig>(DropwizardApp.class, "config.yml");
private static ProviderInfo serviceProvider;
private static Pact testConsumerPact;
@BeforeClass
public static void setupProvider() {
serviceProvider = new ProviderInfo("Dropwizard App");
serviceProvider.setProtocol("http");
serviceProvider.setHost("localhost");
serviceProvider.setPort(8080);
serviceProvider.setPath("/");
ConsumerInfo consumer = new ConsumerInfo();
consumer.setName("test_consumer");
consumer.setPactFile(new File("target/pacts/ping_client-ping_service.json"));
// serviceProvider.getConsumers().add(consumer);
testConsumerPact = (Pact) new PactReader().loadPact(consumer.getPactFile());
}
@Test
@SuppressWarnings("unchecked")
public void runConsumerPacts() {
//grab the first interaction from the pact with consumer
List<Interaction> interactions = scala.collection.JavaConversions.seqAsJavaList(testConsumerPact.interactions());
Interaction interaction1 = interactions.get(0);
//setup any provider state
//setup the client and interaction to fire against the provider
ProviderClient client = new ProviderClient();
client.setProvider(serviceProvider);
client.setRequest(interaction1.request());
Map<String, Object> clientResponse = (Map<String, Object>) client.makeRequest();
Map<String, Object> result = (Map<String, Object>) ResponseComparison.compareResponse(interaction1.response(),
clientResponse, (int) clientResponse.get("statusCode"), (Map) clientResponse.get("headers"), (String) clientResponse.get("data"));
//assert all good
assertThat(result.get("method"), is(true)); // method type matches
Map headers = (Map) result.get("headers"); //headers match
headers.forEach( (k, v) ->
assertThat(format("Header: [%s] does not match", k), v, org.hamcrest.Matchers.equalTo(true))
);
assertThat((Collection<Object>)((Map)result.get("body")).values(), org.hamcrest.Matchers.hasSize(0)); // empty list of body mismatches
}
}
```
### 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 PactJVMProviderSpockSpec extends Specification {
@ClassRule @Shared
TestRule startServiceRule = new DropwizardAppRule<DropwizardAppConfig>(DropwizardApp.class, "config.yml");
@Shared
ProviderInfo serviceProvider
@Shared
Pact testConsumerPact
def setupSpec() {
serviceProvider = new ProviderInfo("Dropwizard App")
serviceProvider.protocol = "http"
serviceProvider.host = "localhost"
serviceProvider.port = 8080;
serviceProvider.path = "/"
def consumer = serviceProvider.hasPactWith("ping_consumer", {
pactFile = new File('target/pacts/ping_client-ping_service.json')
})
testConsumerPact = (Pact) new PactReader().loadPact(consumer.getPactFile());
}
def cleanup() {
//cleanup provider state
//ie. db.truncateAllTables()
}
def cleanupSpec() {
//cleanup provider
}
@Unroll
def "Provider Pact - With Consumer"() {
given:
//setup provider state
// ie. db.setupRecords()
// serviceProvider.requestFilter = { req ->
// req.addHeader('Authorization', token)
// }
when:
ProviderClient client = new ProviderClient(provider: serviceProvider, request: interaction.request())
Map clientResponse = (Map) client.makeRequest()
Map result = (Map) ResponseComparison.compareResponse(interaction.response(),
clientResponse, clientResponse.statusCode, clientResponse.headers, clientResponse.data)
then:
// method matches
result.method == true
// headers all match, spock needs the size checked before
// asserting each result
if (result.headers.size() > 0) {
result.headers.each() { k, v ->
assert v == true
}
}
// empty list of body mismatches
result.body.size() == 0
where:
interaction << scala.collection.JavaConversions.seqAsJavaList(testConsumerPact.interactions())
}
}
```
The newest version!
package au.com.dius.pact.provider
import au.com.dius.pact.matchers.DiffUtils
import au.com.dius.pact.matchers.MatchingConfig
import au.com.dius.pact.model.BodyMismatch
import au.com.dius.pact.model.BodyTypeMismatch
import au.com.dius.pact.model.HeaderMismatch
import au.com.dius.pact.model.OptionalBody
@SuppressWarnings('UnusedImport')
import au.com.dius.pact.model.Response
import au.com.dius.pact.model.ResponseMatching$
import au.com.dius.pact.model.ResponsePartMismatch
import au.com.dius.pact.model.StatusMismatch
import au.com.dius.pact.model.v3.messaging.Message
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.apache.commons.lang3.StringUtils
import org.codehaus.groovy.runtime.powerassert.PowerAssertionError
import scala.None$
import scala.collection.JavaConverters$
/**
* Utility class to compare responses
*/
class ResponseComparison {
Response expected
Map actual
int actualStatus
Map actualHeaders
String actualBody
static compareResponse(Response response, Map actualResponse, int actualStatus, Map actualHeaders,
String actualBody) {
def result = [:]
def comparison = new ResponseComparison(expected: response, actual: actualResponse, actualStatus: actualStatus,
actualHeaders: actualHeaders.collectEntries { k, v -> [k.toUpperCase(), v] }, actualBody: actualBody)
def mismatches = JavaConverters$.MODULE$.seqAsJavaListConverter(
ResponseMatching$.MODULE$.responseMismatches(response, new Response(actualStatus,
actualHeaders, OptionalBody.body(actualBody), [:]))).asJava()
result.method = comparison.compareStatus(mismatches)
result.headers = comparison.compareHeaders(mismatches)
result.body = comparison.compareBody(mismatches)
result
}
static compareMessage(Message message, OptionalBody actual) {
def result = JavaConverters$.MODULE$.mutableMapAsJavaMapConverter(MatchingConfig.bodyMatchers()).asJava().find {
message.contentType ==~ it.key
}
def mismatches = []
def expected = message.asPactRequest()
def actualMessage = new Response(200, ['Content-Type': message.contentType], actual, [:])
if (result) {
mismatches = JavaConverters$.MODULE$.seqAsJavaListConverter(result.value.matchBody(expected,
actualMessage, true)).asJava()
} else {
def expectedBody = message.contents.orElse('')
if (!StringUtils.isEmpty(expectedBody) && StringUtils.isEmpty(actual)) {
mismatches << BodyMismatch.apply(expectedBody, None$.MODULE$.get())
} else if (actual.orElse('') != expectedBody) {
mismatches << BodyMismatch(expectedBody, actual.orElse(''))
}
}
new ResponseComparison(expected: expected, actual: [contentType: [mimeType: message.contentType]],
actualBody: actual.orElse('')).compareBody(mismatches)
}
def compareStatus(List mismatches) {
StatusMismatch statusMismatch = mismatches.find { it instanceof StatusMismatch }
if (statusMismatch) {
int expectedStatus = statusMismatch.expected()
int actualStatus = statusMismatch.actual()
try {
assert expectedStatus == actualStatus
} catch (PowerAssertionError e) {
return e
}
}
true
}
def compareHeaders(List mismatches) {
Map headerResult = [:]
if (expected.headers != null) {
def headerMismatchers = mismatches.findAll { it instanceof HeaderMismatch }.groupBy { it.headerKey }
if (headerMismatchers.empty) {
headerResult = expected.headers.keySet().collectEntries { [it, true] }
} else {
expected.headers.each { headerKey, value ->
if (headerMismatchers[headerKey]) {
headerResult[headerKey] = headerMismatchers[headerKey].first().mismatch.get()
} else {
headerResult[headerKey] = true
}
}
}
}
headerResult
}
def compareBody(List mismatches) {
def result = [:]
BodyTypeMismatch bodyTypeMismatch = mismatches.find { it instanceof BodyTypeMismatch }
if (bodyTypeMismatch) {
result = [comparison: "Expected a response type of '${bodyTypeMismatch.expected()}' but the actual " +
"type was '${bodyTypeMismatch.actual()}'"]
} else if (mismatches.any { it instanceof BodyMismatch }) {
result.comparison = mismatches
.findAll { it instanceof BodyMismatch }
.groupBy { bm -> bm.path() }
.collectEntries { path, m ->
[
path, m.collect { bm ->
[
mismatch: bm.mismatch().defined ? bm.mismatch().get() : 'mismatch',
diff: bm.diff().defined ? bm.diff().get() : ''
]
}
]
}
result.diff = generateFullDiff(actualBody, this.actual.contentType.mimeType as String,
expected.body.present ? expected.body.value : '', expected.jsonBody())
}
result
}
private static generateFullDiff(String actual, String mimeType, String response, Boolean jsonBody) {
String actualBodyString = ''
if (actual) {
if (mimeType ==~ 'application/.*json') {
def bodyMap = new JsonSlurper().parseText(actual)
def bodyJson = JsonOutput.toJson(bodyMap)
actualBodyString = JsonOutput.prettyPrint(bodyJson)
} else {
actualBodyString = actual
}
}
String expectedBodyString = ''
if (response) {
if (jsonBody) {
expectedBodyString = JsonOutput.prettyPrint(response)
} else {
expectedBodyString = response
}
}
DiffUtils.generateDiff(expectedBodyString, actualBodyString)
}
}