diff --git a/compatibility-suite/src/test/groovy/steps/v1/HttpProvider.groovy b/compatibility-suite/src/test/groovy/steps/v1/HttpProvider.groovy index 27dc35791..f55d8e4b6 100644 --- a/compatibility-suite/src/test/groovy/steps/v1/HttpProvider.groovy +++ b/compatibility-suite/src/test/groovy/steps/v1/HttpProvider.groovy @@ -30,11 +30,14 @@ import au.com.dius.pact.provider.ProviderInfo import au.com.dius.pact.provider.ProviderVerifier import au.com.dius.pact.provider.VerificationResult import groovy.json.JsonSlurper +import io.cucumber.datatable.DataTable import io.cucumber.java.After import io.cucumber.java.Scenario import io.cucumber.java.en.Given import io.cucumber.java.en.Then import io.cucumber.java.en.When +import org.apache.hc.core5.http.ClassicHttpRequest +import org.apache.hc.core5.http.io.entity.StringEntity @SuppressWarnings('ThrowRuntimeException') class HttpProvider { @@ -117,6 +120,7 @@ class HttpProvider { verifier = new ProviderVerifier() verifier.projectHasProperty = { name -> verificationProperties.containsKey(name) } verifier.projectGetProperty = { name -> verificationProperties[name] } + verifier.reporters = [ new StubVerificationReporter() ] verificationResults = verifier.verifyProvider(providerInfo) } @@ -263,4 +267,62 @@ class HttpProvider { void the_provider_state_callback_will_not_receive_a_teardown_call() { assert providerStateParams.findAll { p -> p[1] == 'teardown' }.empty } + + @Then('a warning will be displayed that there was no provider state callback configured for provider state {string}') + void a_warning_will_be_displayed_that_there_was_no_provider_state_callback_configured(String state) { + assert verifier.reporters.first().events.find { it.state == state } + } + + @Given('a request filter is configured to make the following changes:') + void a_request_filter_is_configured_to_make_the_following_changes(DataTable dataTable) { + providerInfo.requestFilter = { ClassicHttpRequest request -> + def entry = dataTable.entries().first() + if (entry['path']) { + request.path = entry['path'] + } + + if (entry['headers']) { + entry['headers'].split(',').collect { + it.trim()[1..-2].split(':', 2) + }.collectEntries { + Map.entry(it[0].trim(), it[1].trim()) + }.each { + request.addHeader(it.key.toString(), it.value) + } + } + + if (entry['body']) { + if (entry['body'].startsWith('JSON:')) { + request.addHeader('content-type', 'application/json') + def ct = new org.apache.hc.core5.http.ContentType('application/json', null) + request.entity = new StringEntity(entry['body'][5..-1], ct) + } else if (entry['body'].startsWith('XML:')) { + request.addHeader('content-type', 'application/xml') + def ct = new org.apache.hc.core5.http.ContentType('application/xml', null) + request.entity = new StringEntity(entry['body'][4..-1], ct) + } else { + String contentType = 'text/plain' + if (entry['body'].endsWith('.json')) { + contentType = 'application/json' + } else if (entry['body'].endsWith('.xml')) { + contentType = 'application/xml' + } + request.addHeader('content-type', contentType) + File contents = new File("pact-compatibility-suite/fixtures/${entry['body']}") + contents.withInputStream { + def ct = new org.apache.hc.core5.http.ContentType(contentType, null) + request.entity = new StringEntity(it.text, ct) + } + } + } + } + } + + @Then('the request to the provider will contain the header {string}') + void the_request_to_the_provider_will_contain_the_header(String header) { + def h = header.split(':\\s+', 2) + assert mockProvider.matchedRequests.every { + it.second.headers.containsKey(h[0]) && it.second.headers[h[0]][0] == h[1] + } + } } diff --git a/compatibility-suite/src/test/groovy/steps/v1/StubVerificationReporter.groovy b/compatibility-suite/src/test/groovy/steps/v1/StubVerificationReporter.groovy new file mode 100644 index 000000000..087d00010 --- /dev/null +++ b/compatibility-suite/src/test/groovy/steps/v1/StubVerificationReporter.groovy @@ -0,0 +1,135 @@ +package steps.v1 + +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.UrlPactSource +import au.com.dius.pact.provider.IConsumerInfo +import au.com.dius.pact.provider.IProviderInfo +import au.com.dius.pact.provider.IProviderVerifier +import au.com.dius.pact.provider.VerificationResult +import au.com.dius.pact.provider.reporters.BaseVerifierReporter + +@SuppressWarnings('GetterMethodCouldBeProperty') +class StubVerificationReporter extends BaseVerifierReporter { + List> events = [] + + @Override + String getExt() { null } + + @Override + File getReportDir() { null } + + @Override + void setReportDir(File file) { } + + @Override + File getReportFile() { null } + + @Override + void setReportFile(File file) { } + + @Override + IProviderVerifier getVerifier() { null } + + @Override + void setVerifier(IProviderVerifier iProviderVerifier) { } + + @Override + void initialise(IProviderInfo provider) { } + + @Override + void finaliseReport() { } + + @Override + void reportVerificationForConsumer(IConsumerInfo consumer, IProviderInfo provider, String tag) { } + + @Override + void verifyConsumerFromUrl(UrlPactSource pactUrl, IConsumerInfo consumer) { } + + @Override + void verifyConsumerFromFile(PactSource pactFile, IConsumerInfo consumer) { } + + @Override + void pactLoadFailureForConsumer(IConsumerInfo consumer, String message) { } + + @Override + void warnProviderHasNoConsumers(IProviderInfo provider) { } + + @Override + void warnPactFileHasNoInteractions(Pact pact) { } + + @Override + void interactionDescription(Interaction interaction) { } + + @Override + void stateForInteraction(String state, IProviderInfo provider, IConsumerInfo consumer, boolean isSetup) { } + + @Override + void warnStateChangeIgnored(String state, IProviderInfo provider, IConsumerInfo consumer) { + events << [state: state, provider: provider, consumer: consumer] + } + + @Override + void stateChangeRequestFailedWithException(String state, boolean isSetup, Exception e, boolean printStackTrace) { } + + @Override + void stateChangeRequestFailed(String state, IProviderInfo provider, boolean isSetup, String httpStatus) { } + + @Override + void warnStateChangeIgnoredDueToInvalidUrl(String s, IProviderInfo p, boolean isSetup, Object stateChangeHandler) { } + + @Override + void requestFailed(IProviderInfo p, Interaction i, String message, Exception e, boolean printStackTrace) { } + + @Override + void returnsAResponseWhich() { } + + @Override + void statusComparisonOk(int status) { } + + @Override + void statusComparisonFailed(int status, Object comparison) { } + + @Override + void includesHeaders() { } + + @Override + void headerComparisonOk(String key, List value) { } + + @Override + void headerComparisonFailed(String key, List value, Object comparison) { } + + @Override + void bodyComparisonOk() { } + + @Override + void bodyComparisonFailed(Object comparison) { } + + @Override + void errorHasNoAnnotatedMethodsFoundForInteraction(Interaction interaction) { } + + @Override + void verificationFailed(Interaction interaction, Exception e, boolean printStackTrace) { } + + @Override + void generatesAMessageWhich() { } + + @Override + void displayFailures(Map failures) { } + + @Override + void displayFailures(List failures) { } + + @Override + void includesMetadata() { } + + @Override + void metadataComparisonOk() { } + + @Override + void metadataComparisonOk(String key, Object value) { } + + @Override + void metadataComparisonFailed(String key, Object value, Object comparison) { } +} diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/MockHttpServer.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/MockHttpServer.kt index 17a3da974..b16b17fd2 100755 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/MockHttpServer.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/MockHttpServer.kt @@ -111,7 +111,7 @@ abstract class AbstractBaseMockServer : MockServer { abstract class BaseMockServer(val pact: BasePact, val config: MockProviderConfig) : AbstractBaseMockServer() { val mismatchedRequests = ConcurrentHashMap>() - val matchedRequests = ConcurrentLinkedQueue() + val matchedRequests = ConcurrentLinkedQueue>() private val requestMatcher = RequestMatching(pact) override fun waitForServer() { @@ -184,10 +184,11 @@ abstract class BaseMockServer(val pact: BasePact, val config: MockProviderConfig if (mismatchedRequests.isNotEmpty()) { return PactVerificationResult.Mismatches(mismatchedRequests.values.flatten()) } + val receivedRequests = matchedRequests.map { it.first } val expectedRequests = pact.interactions.asSequence() .filter { it.isSynchronousRequestResponse() } .map { it.asSynchronousRequestResponse()!!.request } - .filter { !matchedRequests.contains(it) } + .filter { !receivedRequests.contains(it) } .toList() if (expectedRequests.isNotEmpty()) { return PactVerificationResult.ExpectedButNotReceived(expectedRequests) @@ -199,7 +200,7 @@ abstract class BaseMockServer(val pact: BasePact, val config: MockProviderConfig when (val matchResult = requestMatcher.matchInteraction(request)) { is FullRequestMatch -> { val interaction = matchResult.interaction - matchedRequests.add(interaction.request) + matchedRequests.add(interaction.request to request) return DefaultResponseGenerator.generateResponse(interaction.response, mutableMapOf( "mockServer" to mapOf("href" to getUrl(), "port" to getPort()),