Merge branch '1.0.x'

This commit is contained in:
Marcin Grzejszczak
2017-04-26 13:44:37 +02:00
9 changed files with 263 additions and 19 deletions

View File

@@ -111,7 +111,7 @@ org.springframework.cloud.contract.spec.Contract.make {
method 'PUT' // (2)
url '/fraudcheck' // (3)
body([ // (4)
clientId: $(regex('[0-9]{10}')),
"client.id": $(regex('[0-9]{10}')),
loanAmount: 99999
])
headers { // (5)
@@ -122,7 +122,7 @@ org.springframework.cloud.contract.spec.Contract.make {
status 200 // (7)
body([ // (8)
fraudCheckStatus: "FRAUD",
rejectionReason: "Amount too high"
"rejection.reason": "Amount too high"
])
headers { // (9)
contentType('application/json')
@@ -381,7 +381,7 @@ org.springframework.cloud.contract.spec.Contract.make {
method 'PUT' // (2)
url '/fraudcheck' // (3)
body([ // (4)
clientId: $(regex('[0-9]{10}')),
"client.id": $(regex('[0-9]{10}')),
loanAmount: 99999
])
headers { // (5)
@@ -392,7 +392,7 @@ org.springframework.cloud.contract.spec.Contract.make {
status 200 // (7)
body([ // (8)
fraudCheckStatus: "FRAUD",
rejectionReason: "Amount too high"
"rejection.reason": "Amount too high"
])
headers { // (9)
contentType('application/json')

View File

@@ -100,6 +100,7 @@ After that rule gets executed Stub Runner connects to your Maven repository and
- unzip them to a temporary folder
- start a WireMock server for each Maven dependency on a random port from the provided range of ports / provided port
- feed the WireMock server with all JSON files that are valid WireMock definitions
- can also send messages (remember to pass an implementation of `MessageVerifier` interface)
Stub Runner uses https://wiki.eclipse.org/Aether[Eclipse Aether] mechanism to download the Maven dependencies.
Check their https://wiki.eclipse.org/Aether[docs] for more information.
@@ -127,6 +128,10 @@ include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/Stu
Check the *Common properties for JUnit and Spring* for more information on how to apply global configuration of Stub Runner.
IMPORTANT: To use the JUnit rule together with messaging you have to provide an implementation of the
`MessageVerifier` interface to the rule builder (e.g. `rule.messageVerifier(new MyMessageVerifier())`).
If you don't do this then whenever you try to send a message an exception will be thrown.
==== Maven settings
The stub downloader honors Maven settings for a different local repository folder.

View File

@@ -32,7 +32,11 @@ public class BatchStubRunnerFactory {
private final MessageVerifier<?> contractVerifierMessaging;
public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions) {
this(stubRunnerOptions, aetherStubDownloader(stubRunnerOptions), new NoOpStubMessages());
this(stubRunnerOptions, new NoOpStubMessages());
}
public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, MessageVerifier verifier) {
this(stubRunnerOptions, aetherStubDownloader(stubRunnerOptions), verifier);
}
private static StubDownloader aetherStubDownloader(StubRunnerOptions stubRunnerOptions) {

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.contract.stubrunner;
import groovy.json.JsonOutput;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
@@ -38,8 +40,6 @@ import org.springframework.cloud.contract.verifier.messaging.MessageVerifier;
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages;
import org.springframework.cloud.contract.verifier.util.BodyExtractor;
import groovy.json.JsonOutput;
/**
* Runs stubs for a particular {@link StubServer}
*/
@@ -173,7 +173,7 @@ class StubRunnerExecutor implements StubFinder {
private boolean triggerForDsls(Collection<Contract> dsls, String labelName) {
Collection<Contract> matchingDsls = new ArrayList<>();
for (Contract contract : dsls) {
if (labelName.equals(contract.getLabel())) {
if (labelName.equals(contract.getLabel()) && contract.getOutputMessage() != null) {
matchingDsls.add(contract);
}
}
@@ -181,7 +181,7 @@ class StubRunnerExecutor implements StubFinder {
return false;
}
for (Contract contract : matchingDsls) {
sendMessageIfApplicable(contract);
sendMessage(contract);
}
return true;
}
@@ -190,10 +190,17 @@ class StubRunnerExecutor implements StubFinder {
public boolean trigger() {
Collection<Contract> matchingContracts = new ArrayList<>();
for (Collection<Contract> it : getContracts().values()) {
matchingContracts.addAll(it);
for (Contract contract : it) {
if (contract.getOutputMessage() != null) {
matchingContracts.add(contract);
}
}
}
if (matchingContracts.isEmpty()) {
return false;
}
for (Contract contract : matchingContracts) {
sendMessageIfApplicable(contract);
sendMessage(contract);
}
return true;
}
@@ -213,11 +220,8 @@ class StubRunnerExecutor implements StubFinder {
return labels;
}
private void sendMessageIfApplicable(Contract groovyDsl) {
private void sendMessage(Contract groovyDsl) {
OutputMessage outputMessage = groovyDsl.getOutputMessage();
if (outputMessage == null) {
return;
}
DslProperty<?> body = outputMessage.getBody();
Headers headers = outputMessage.getHeaders();
this.contractVerifierMessaging.send(

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -33,6 +34,7 @@ import org.springframework.cloud.contract.stubrunner.StubConfiguration;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.cloud.contract.stubrunner.StubRunnerOptions;
import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder;
import org.springframework.cloud.contract.verifier.messaging.MessageVerifier;
/**
* JUnit class rule that allows you to download the provided stubs.
@@ -45,6 +47,7 @@ public class StubRunnerRule implements TestRule, StubFinder {
private final StubRunnerOptionsBuilder stubRunnerOptionsBuilder = new StubRunnerOptionsBuilder(defaultStubRunnerOptions());
private BatchStubRunner stubFinder;
private MessageVerifier verifier = new ExceptionThrowingMessageVerifier();
@Override
public Statement apply(final Statement base, Description description) {
@@ -58,7 +61,8 @@ public class StubRunnerRule implements TestRule, StubFinder {
private void before() {
StubRunnerRule.this.stubFinder = new BatchStubRunnerFactory(
StubRunnerRule.this.stubRunnerOptionsBuilder.build()).buildBatchStubRunner();
StubRunnerRule.this.stubRunnerOptionsBuilder.build(),
StubRunnerRule.this.verifier).buildBatchStubRunner();
StubRunnerRule.this.stubFinder.runStubs();
}
};
@@ -81,6 +85,16 @@ public class StubRunnerRule implements TestRule, StubFinder {
return builder.build();
}
/**
* Pass the {@link MessageVerifier} that this rule should use.
* If you don't pass anything a {@link ExceptionThrowingMessageVerifier} will be used.
* That means that an exception will be thrown whenever you try to do sth messaging related.
*/
public StubRunnerRule messageVerifier(MessageVerifier messageVerifier) {
this.verifier = messageVerifier;
return this;
}
/**
* Override all options
*
@@ -209,17 +223,29 @@ public class StubRunnerRule implements TestRule, StubFinder {
@Override
public boolean trigger(String ivyNotation, String labelName) {
return this.stubFinder.trigger(ivyNotation, labelName);
boolean result = this.stubFinder.trigger(ivyNotation, labelName);
if (!result) {
throw new IllegalStateException("Failed to trigger a message with notation [" + ivyNotation + "] and label [" + labelName + "]");
}
return result;
}
@Override
public boolean trigger(String labelName) {
return this.stubFinder.trigger(labelName);
boolean result = this.stubFinder.trigger(labelName);
if (!result) {
throw new IllegalStateException("Failed to trigger a message with label [" + labelName + "]");
}
return result;
}
@Override
public boolean trigger() {
return this.stubFinder.trigger();
boolean result = this.stubFinder.trigger();
if (!result) {
throw new IllegalStateException("Failed to trigger a message");
}
return result;
}
@Override
@@ -227,4 +253,27 @@ public class StubRunnerRule implements TestRule, StubFinder {
return this.stubFinder.labels();
}
static class ExceptionThrowingMessageVerifier implements MessageVerifier {
private static final String EXCEPTION_MESSAGE = "Please provide a custom MessageVerifier to use this feature";
@Override public void send(Object message, String destination) {
throw new UnsupportedOperationException(EXCEPTION_MESSAGE);
}
@Override public Object receive(String destination, long timeout,
TimeUnit timeUnit) {
throw new UnsupportedOperationException(EXCEPTION_MESSAGE);
}
@Override public Object receive(String destination) {
throw new UnsupportedOperationException(EXCEPTION_MESSAGE);
}
@Override public void send(Object payload, Map headers, String destination) {
throw new UnsupportedOperationException(EXCEPTION_MESSAGE);
}
}
}

View File

@@ -116,6 +116,21 @@ class StubRunnerExecutorSpec extends Specification {
executor.shutdown()
}
def 'should return false if no messages are found'() {
given:
def stubConf = new StubConfiguration('asd', 'asd', 'asd', '')
StubRunnerExecutor executor = new StubRunnerExecutor(portScanner)
when:
executor.runStubs(stubRunnerOptions,
new StubRepository(new File('src/test/resources/repository/httpcontract')), stubConf)
then:
!executor.trigger()
!executor.trigger("missing", "label")
!executor.trigger("label")
cleanup:
executor.shutdown()
}
Map<StubConfiguration, Integer> stubIdsWithPortsFromString(String stubIdsToPortMapping) {
return stubIdsToPortMapping.split(',').collectEntries { String entry ->
return StubsParser.fromStringWithPort(entry)

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.junit
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.ClassRule
import org.springframework.cloud.contract.verifier.messaging.MessageVerifier
import spock.lang.Shared
import spock.lang.Specification
import java.util.concurrent.TimeUnit
/**
* @author Marcin Grzejszczak
*/
class StubRunnerRuleCustomMsgVerifierSpec extends Specification {
@BeforeClass
@AfterClass
void setupProps() {
System.clearProperty("stubrunner.repository.root")
System.clearProperty("stubrunner.classifier")
}
@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule()
.repoRoot(StubRunnerRuleCustomMsgVerifierSpec.getResource("/m2repo/repository").toURI().toString())
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "bootService")
.messageVerifier(new MyMessageVerifier())
def 'should use the provided message verifier in the junit rule'() {
when:
rule.trigger()
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("Failed to send a message with headers")
when:
rule.trigger("return_book_1")
then:
e = thrown(IllegalStateException)
e.message.contains("Failed to send a message with headers")
when:
rule.trigger("bootService", "return_book_1")
then:
e = thrown(IllegalStateException)
e.message.contains("Failed to send a message with headers")
}
static class MyMessageVerifier implements MessageVerifier {
@Override
void send(Object message, String destination) {
throw new IllegalStateException("Failed to send a message")
}
@Override
Object receive(String destination, long timeout, TimeUnit timeUnit) {
throw new IllegalStateException("Failed to receive a message with timeout")
}
@Override
Object receive(String destination) {
throw new IllegalStateException("Failed to receive a message")
}
@Override
void send(Object payload, Map headers, String destination) {
throw new IllegalStateException("Failed to send a message with headers")
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.junit
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
class StubRunnerRuleExceptionThrowingSpec extends Specification {
@BeforeClass
@AfterClass
void setupProps() {
System.clearProperty("stubrunner.repository.root")
System.clearProperty("stubrunner.classifier")
}
@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule()
.repoRoot(StubRunnerRuleExceptionThrowingSpec.getResource("/m2repo/repository").toURI().toString())
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "bootService")
def 'should throw exception when no message verifier was passed and message related method was triggered'() {
when:
rule.trigger()
then:
thrown(UnsupportedOperationException)
when:
rule.trigger("return_book_1")
then:
thrown(UnsupportedOperationException)
when:
rule.trigger("bootService", "return_book_1")
then:
thrown(UnsupportedOperationException)
}
}

View File

@@ -0,0 +1,27 @@
org.springframework.cloud.contract.spec.Contract.make {
request {
method """PUT"""
url """/fraudcheck"""
body("""
{
"clientPesel":"${value(consumer(regex('[0-9]{10}')), producer('1234567890'))}",
"loanAmount":99999}
"""
)
headers {
contentType("application/vnd.fraud.v1+json")
}
}
response {
status 200
body( """{
"fraudCheckStatus": "${value(c('FRAUD'), p(regex('[A-Z]{5}')))}",
"rejectionReason": "Amount too high"
}""")
headers {
contentType("application/vnd.fraud.v1+json")
}
}
}