Merge branch '1.0.x'

Stub / Test Matchers (#186)
Without this change we're forcing users to embed their dynamic properties inside the body. For some this is natural and acceptable, but especially for the users coming from the Pact world this sounds bizarre. Also some other people have a problem with remembering who the consumer / producer is etc.

With this change we're introducing the stubMatchers and testMatchers section. Thanks to this one can separate the body from defining the dynamic properties. Especially for Pact users this is more natural. Speaking of which this is a prerequisite for #96

fixes #185
This commit is contained in:
Marcin Grzejszczak
2017-01-10 17:21:38 +01:00
25 changed files with 1346 additions and 54 deletions

View File

@@ -22,17 +22,19 @@ import java.util.regex.Pattern;
import org.apache.camel.Exchange;
import org.apache.camel.Predicate;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.toomuchcoding.jsonassert.JsonAssertion;
import com.toomuchcoding.jsonassert.JsonVerifiable;
/**
* Passes through a message that matches the one defined in the DSL
@@ -54,9 +56,13 @@ class StubRunnerCamelPredicate implements Predicate {
return false;
}
Object inputMessage = exchange.getIn().getBody();
BodyMatchers matchers = this.groovyDsl.getInput().getMatchers();
Object dslBody = MapConverter.getStubSideValues(this.groovyDsl.getInput().getMessageBody());
Object matchingInputMessage = JsonToJsonPathsConverter
.removeMatchingJsonPaths(dslBody, matchers);
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(
this.groovyDsl.getInput().getMessageBody());
matchingInputMessage);
DocumentContext parsedJson;
try {
parsedJson = JsonPath
@@ -67,14 +73,20 @@ class StubRunnerCamelPredicate implements Predicate {
}
boolean matches = true;
for (MethodBufferingJsonVerifiable path : jsonPaths) {
matches &= matchesJsonPath(parsedJson, path);
matches &= matchesJsonPath(parsedJson, path.jsonPath());
}
if (matchers != null && matchers.hasMatchers()) {
for (BodyMatcher matcher : matchers.jsonPathMatchers()) {
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher.path(), matcher.value());
matches &= matchesJsonPath(parsedJson, jsonPath);
}
}
return matches;
}
private boolean matchesJsonPath(DocumentContext parsedJson, JsonVerifiable jsonVerifiable) {
private boolean matchesJsonPath(DocumentContext parsedJson, String jsonPath) {
try {
JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonVerifiable.jsonPath());
JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonPath);
return true;
} catch (Exception e) {
return false;

View File

@@ -20,10 +20,13 @@ import java.util.Map;
import java.util.regex.Pattern;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import org.springframework.integration.core.MessageSelector;
import org.springframework.messaging.Message;
@@ -32,7 +35,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.toomuchcoding.jsonassert.JsonAssertion;
import com.toomuchcoding.jsonassert.JsonVerifiable;
/**
* Passes through a message that matches the one defined in the DSL
@@ -54,9 +56,13 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
return false;
}
Object inputMessage = message.getPayload();
BodyMatchers matchers = this.groovyDsl.getInput().getMatchers();
Object dslBody = MapConverter.getStubSideValues(this.groovyDsl.getInput().getMessageBody());
Object matchingInputMessage = JsonToJsonPathsConverter
.removeMatchingJsonPaths(dslBody, matchers);
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(
this.groovyDsl.getInput().getMessageBody());
matchingInputMessage);
DocumentContext parsedJson;
try {
parsedJson = JsonPath.parse(this.objectMapper.writeValueAsString(inputMessage));
@@ -66,16 +72,21 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
}
boolean matches = true;
for (MethodBufferingJsonVerifiable path : jsonPaths) {
matches &= matchesJsonPath(parsedJson, path);
matches &= matchesJsonPath(parsedJson, path.jsonPath());
}
if (matchers != null && matchers.hasMatchers()) {
for (BodyMatcher matcher : matchers.jsonPathMatchers()) {
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher.path(), matcher.value());
matches &= matchesJsonPath(parsedJson, jsonPath);
}
}
return matches;
}
private boolean matchesJsonPath(DocumentContext parsedJson,
JsonVerifiable jsonVerifiable) {
private boolean matchesJsonPath(DocumentContext parsedJson, String jsonPath) {
try {
JsonAssertion.assertThat(parsedJson)
.matchesJsonPath(jsonVerifiable.jsonPath());
.matchesJsonPath(jsonPath);
return true;
}
catch (Exception e) {

View File

@@ -20,10 +20,13 @@ import java.util.Map;
import java.util.regex.Pattern;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import org.springframework.integration.core.MessageSelector;
import org.springframework.messaging.Message;
@@ -32,7 +35,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.toomuchcoding.jsonassert.JsonAssertion;
import com.toomuchcoding.jsonassert.JsonVerifiable;
/**
* Passes through a message that matches the one defined in the DSL
@@ -50,31 +52,40 @@ class StubRunnerStreamMessageSelector implements MessageSelector {
@Override
public boolean accept(Message<?> message) {
if(!headersMatch(message)){
if (!headersMatch(message)) {
return false;
}
Object inputMessage = message.getPayload();
BodyMatchers matchers = this.groovyDsl.getInput().getMatchers();
Object dslBody = MapConverter.getStubSideValues(this.groovyDsl.getInput().getMessageBody());
Object matchingInputMessage = JsonToJsonPathsConverter
.removeMatchingJsonPaths(dslBody, matchers);
JsonPaths jsonPaths = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(
this.groovyDsl.getInput().getMessageBody());
matchingInputMessage);
DocumentContext parsedJson;
try {
parsedJson = JsonPath.parse(this.objectMapper.writeValueAsString(inputMessage));
for (MethodBufferingJsonVerifiable it : jsonPaths) {
if (!matchesJsonPath(parsedJson, it)) {
return false;
}
}
}
catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot parse JSON", e);
throw new IllegalStateException("Cannot serialize to JSON", e);
}
return true;
boolean matches = true;
for (MethodBufferingJsonVerifiable path : jsonPaths) {
matches &= matchesJsonPath(parsedJson, path.jsonPath());
}
if (matchers != null && matchers.hasMatchers()) {
for (BodyMatcher matcher : matchers.jsonPathMatchers()) {
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher.path(), matcher.value());
matches &= matchesJsonPath(parsedJson, jsonPath);
}
}
return matches;
}
private boolean matchesJsonPath(DocumentContext parsedJson, JsonVerifiable jsonVerifiable) {
private boolean matchesJsonPath(DocumentContext parsedJson, String jsonPath) {
try {
JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonVerifiable.jsonPath());
JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonPath);
return true;
} catch (Exception e) {
return false;

View File

@@ -0,0 +1,137 @@
package org.springframework.cloud.contract.stubrunner.messaging.camel
import org.apache.camel.Exchange
import org.apache.camel.Message
import org.springframework.cloud.contract.spec.Contract
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
class StubRunnerCamelPredicateSpec extends Specification {
Exchange exchange = Stub(Exchange)
Message message = Stub(Message)
def "should return false if headers don't match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageBody(foo: "bar")
messageHeaders {
header("foo", $(c(regex("[0-9]{3}")), p(123)))
}
}
}
and:
StubRunnerCamelPredicate predicate = new StubRunnerCamelPredicate(dsl)
exchange.in >> message
message.headers >> [
foo: "non matching stuff"
]
expect:
!predicate.matches(exchange)
}
def "should return false if headers match and body doesn't"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: $(c(regex("[0-9]{3}")), p(123)))
}
}
and:
StubRunnerCamelPredicate predicate = new StubRunnerCamelPredicate(dsl)
exchange.in >> message
message.headers >> [
foo: 123
]
message.body >> [
foo: "non matching stuff"
]
expect:
!predicate.matches(exchange)
}
def "should return false if headers match and body doesn't when it's using matchers"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: "non matching stuff")
stubMatchers {
jsonPath('$.foo', byRegex("[0-9]{3}"))
}
}
}
and:
StubRunnerCamelPredicate predicate = new StubRunnerCamelPredicate(dsl)
exchange.in >> message
message.headers >> [
foo: 123
]
message.body >> [
foo: "non matching stuff"
]
expect:
!predicate.matches(exchange)
}
def "should return true if headers and body match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: $(c(regex("[0-9]{3}")), p(123)))
}
}
and:
StubRunnerCamelPredicate predicate = new StubRunnerCamelPredicate(dsl)
exchange.in >> message
message.headers >> [
foo: 123
]
message.body >> [
foo: 123
]
expect:
predicate.matches(exchange)
}
def "should return true if headers and body using matchers match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: 123)
stubMatchers {
jsonPath('$.foo', byRegex("[0-9]{3}"))
}
}
}
and:
StubRunnerCamelPredicate predicate = new StubRunnerCamelPredicate(dsl)
exchange.in >> message
message.headers >> [
foo: 123
]
message.body >> [
foo: 123
]
expect:
predicate.matches(exchange)
}
}

View File

@@ -0,0 +1,130 @@
package org.springframework.cloud.contract.stubrunner.messaging.integration
import org.springframework.cloud.contract.spec.Contract
import org.springframework.messaging.Message
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
class StubRunnerIntegrationMessageSelectorSpec extends Specification {
Message message = Mock(Message)
def "should return false if headers don't match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageBody(foo: "bar")
messageHeaders {
header("foo", $(c(regex("[0-9]{3}")), p(123)))
}
}
}
and:
StubRunnerIntegrationMessageSelector predicate = new StubRunnerIntegrationMessageSelector(dsl)
message.headers >> [
foo: "non matching stuff"
]
expect:
!predicate.accept(message)
}
def "should return false if headers match and body doesn't"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: $(c(regex("[0-9]{3}")), p(123)))
}
}
and:
StubRunnerIntegrationMessageSelector predicate = new StubRunnerIntegrationMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: "non matching stuff"
]
expect:
!predicate.accept(message)
}
def "should return false if headers match and body doesn't when it's using matchers"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: "non matching stuff")
stubMatchers {
jsonPath('$.foo', byRegex("[0-9]{3}"))
}
}
}
and:
StubRunnerIntegrationMessageSelector predicate = new StubRunnerIntegrationMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: "non matching stuff"
]
expect:
!predicate.accept(message)
}
def "should return true if headers and body match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: $(c(regex("[0-9]{3}")), p(123)))
}
}
and:
StubRunnerIntegrationMessageSelector predicate = new StubRunnerIntegrationMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: 123
]
expect:
predicate.accept(message)
}
def "should return true if headers and body using matchers match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: 123)
stubMatchers {
jsonPath('$.foo', byRegex("[0-9]{3}"))
}
}
}
and:
StubRunnerIntegrationMessageSelector predicate = new StubRunnerIntegrationMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: 123
]
expect:
predicate.accept(message)
}
}

View File

@@ -0,0 +1,131 @@
package org.springframework.cloud.contract.stubrunner.messaging.stream
import org.springframework.cloud.contract.spec.Contract
import org.springframework.messaging.Message
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
class StubRunnerStreamMessageSelectorSpec extends Specification {
Message message = Mock(Message)
def "should return false if headers don't match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageBody(foo: "bar")
messageHeaders {
header("foo", $(c(regex("[0-9]{3}")), p(123)))
}
}
}
and:
StubRunnerStreamMessageSelector predicate = new StubRunnerStreamMessageSelector(dsl)
message.headers >> [
foo: "non matching stuff"
]
expect:
!predicate.accept(message)
}
def "should return false if headers match and body doesn't"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: $(c(regex("[0-9]{3}")), p(123)))
}
}
and:
StubRunnerStreamMessageSelector predicate = new StubRunnerStreamMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: "non matching stuff"
]
expect:
!predicate.accept(message)
}
def "should return false if headers match and body doesn't when it's using matchers"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: "non matching stuff")
stubMatchers {
jsonPath('$.foo', byRegex("[0-9]{3}"))
}
}
}
and:
StubRunnerStreamMessageSelector predicate = new StubRunnerStreamMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: "non matching stuff"
]
expect:
!predicate.accept(message)
}
def "should return true if headers and body match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: $(c(regex("[0-9]{3}")), p(123)))
}
}
and:
StubRunnerStreamMessageSelector predicate = new StubRunnerStreamMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: 123
]
expect:
predicate.accept(message)
}
def "should return true if headers and body using matchers match"() {
given:
Contract dsl = Contract.make {
input {
messageFrom "foo"
messageHeaders {
header("foo", 123)
}
messageBody(foo: 123)
stubMatchers {
jsonPath('$.foo', byRegex("[0-9]{3}"))
}
}
}
and:
StubRunnerStreamMessageSelector predicate = new StubRunnerStreamMessageSelector(dsl)
message.headers >> [
foo: 123
]
message.payload >> [
foo: 123
]
expect:
predicate.accept(message)
}
}