Merge branch '1.0.x'

fixes #135
This commit is contained in:
Marcin Grzejszczak
2016-12-29 15:38:45 +01:00
8 changed files with 297 additions and 9 deletions

View File

@@ -1,7 +1,7 @@
// Do not edit this file (e.g. go instead to src/main/asciidoc)
image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-contract?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-contract"]
image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/1.0.x/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-contract"]
image::https://circleci.com/gh/spring-cloud/spring-cloud-contract.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-contract"]
== Spring Cloud Contract
@@ -1318,7 +1318,7 @@ Example of a `pom.xml` inside the `server` folder.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.BUILD-SNAPSHOT</version>
<version>1.5.0.BUILD-SNAPSHOT</version>
<relativePath />
</parent>
@@ -1566,17 +1566,12 @@ follow the guidelines below.
=== Sign the Contributor License Agreement
Before we accept a non-trivial patch or pull request we will need you to sign the
https://cla.pivotal.io/sign/spring[Contributor License Agreement].
https://support.springsource.com/spring_committer_signup[contributor's agreement].
Signing the contributor's agreement does not grant anyone commit rights to the main
repository, but it does mean that we can accept your contributions, and you will get an
author credit if we do. Active contributors might be asked to join the core team, and
given the ability to merge pull requests.
=== Code of Conduct
This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc[code of
conduct]. By participating, you are expected to uphold this code. Please report
unacceptable behavior to spring-code-of-conduct@pivotal.io.
=== Code Conventions and Housekeeping
None of these is essential for a pull request, but they will all help. They can also be
added after the original pull request but before a merge.
@@ -1584,7 +1579,7 @@ added after the original pull request but before a merge.
* Use the Spring Framework code format conventions. If you use Eclipse
you can import formatter settings using the
`eclipse-code-formatter.xml` file from the
https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring
https://github.com/spring-cloud/build/tree/master/eclipse-coding-conventions.xml[Spring
Cloud Build] project. If using IntelliJ, you can use the
http://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter
Plugin] to import the same file.

View File

@@ -23,18 +23,24 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.xml.xpath.XPathExpressionException;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.MatcherAssert;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder;
import org.springframework.test.web.client.RequestMatcher;
import org.springframework.test.web.client.ResponseActions;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import org.springframework.test.web.client.response.DefaultResponseCreator;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
@@ -44,8 +50,12 @@ import com.github.tomakehurst.wiremock.common.Json;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.http.MultiValue;
import com.github.tomakehurst.wiremock.http.ResponseDefinition;
import com.github.tomakehurst.wiremock.matching.MatchResult;
import com.github.tomakehurst.wiremock.matching.MatchesJsonPathPattern;
import com.github.tomakehurst.wiremock.matching.MatchesXPathPattern;
import com.github.tomakehurst.wiremock.matching.MultiValuePattern;
import com.github.tomakehurst.wiremock.matching.RequestPattern;
import com.github.tomakehurst.wiremock.matching.StringValuePattern;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
@@ -192,12 +202,47 @@ public class WireMockRestServiceServer {
for (StubMapping mapping : mappings) {
ResponseActions expect = server.expect(requestTo(request(mapping.getRequest())));
expect.andExpect(method(HttpMethod.valueOf(mapping.getRequest().getMethod().getName())));
mapping.getRequest().getBodyPatterns();
bodyPatterns(expect, mapping.getRequest());
requestHeaders(expect, mapping.getRequest());
expect.andRespond(response(mapping.getResponse()));
}
return server;
}
private void bodyPatterns(ResponseActions expect, RequestPattern request) {
if (request.getBodyPatterns() == null) {
return;
}
for (final StringValuePattern pattern : request.getBodyPatterns()) {
if (pattern instanceof MatchesJsonPathPattern) {
expect.andExpect(MockRestRequestMatchers.jsonPath(((MatchesJsonPathPattern) pattern).getMatchesJsonPath()).exists());
} else if (pattern instanceof MatchesXPathPattern) {
expect.andExpect(xpath((MatchesXPathPattern) pattern));
}
expect.andExpect(matchContents(pattern));
}
}
private RequestMatcher matchContents(final StringValuePattern pattern) {
return new RequestMatcher() {
@Override public void match(ClientHttpRequest request)
throws IOException, AssertionError {
MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;;
MatchResult result = pattern.match(mockRequest.getBodyAsString());
MatcherAssert.assertThat("Request as string [" + mockRequest.getBodyAsString() + "]", result.isExactMatch());
}
};
}
private RequestMatcher xpath(MatchesXPathPattern pattern) {
try {
return MockRestRequestMatchers.xpath(pattern.getMatchesXPath()).exists();
} catch (XPathExpressionException e) {
throw new IllegalStateException(e);
}
}
private String request(RequestPattern request) {
return this.baseUrl + (request.getUrlPath() == null ? (request.getUrl() == null ? "/" : request.getUrl())
: request.getUrlPath());

View File

@@ -1,6 +1,8 @@
package org.springframework.cloud.contract.wiremock;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.http.MediaType;
@@ -9,6 +11,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonCreator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
@@ -217,4 +221,183 @@ public class WiremockMockServerApplicationTests {
String.class)).isEqualTo("Hello World");
}
// Issue #135
@Test
public void postWithBodyMatchingJsonPaths() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-jsonpath.json").build();
assertThat(this.restTemplate.postForObject("http://example.org/body",
new Things(Collections.singletonList(new Thing("RequiredThing"))), String.class))
.isEqualTo("Hello Body");
}
@Test
public void postWithRequestThatHasNonMatchingJsonPaths() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-jsonpath.json").build();
String response;
try {
response = this.restTemplate.postForObject("http://example.org/body",
new Things(Collections.singletonList(new Thing("AbsentThing"))), String.class);
} catch (AssertionError e) {
response = null;
}
if (null != response) {
fail("There was a response for a request that shouldn't be matched : " + response);
}
}
@Test
public void postWithBodyMatchingXPath() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-xpath.json").build();
assertThat(this.restTemplate.exchange(
RequestEntity.post(URI.create("http://example.org/body"))
.contentType(MediaType.APPLICATION_XML)
.body("<things><thing><name>RequiredThing</name></thing></things>"),
String.class).getBody()).isEqualTo("Hello Body");
}
@Test
public void postWithRequestThatHasNonMatchingXPaths() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-xpath.json").build();
ResponseEntity<String> response;
try {
response = this.restTemplate.exchange(
RequestEntity.post(URI.create("http://example.org/body"))
.contentType(MediaType.APPLICATION_XML)
.body("<foo/>"),
String.class);
} catch (AssertionError e) {
response = null;
}
if (null != response) {
fail("There was a response for a request that shouldn't be matched : " + response);
}
}
@Test
public void postWithBodyMatchingJson() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-equaltojson.json").build();
assertThat(this.restTemplate.postForObject("http://example.org/body",
new Things(Collections.singletonList(new Thing("RequiredThing"))), String.class))
.isEqualTo("Hello Body");
}
@Test
public void postWithRequestThatHasNonMatchingJson() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-equaltojson.json").build();
String response;
try {
response = this.restTemplate.postForObject("http://example.org/body",
new Things(Collections.singletonList(new Thing("AbsentThing"))), String.class);
} catch (AssertionError e) {
response = null;
}
if (null != response) {
fail("There was a response for a request that shouldn't be matched : " + response);
}
}
@Test
public void postWithBodyMatchingXml() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-equaltoxml.json").build();
assertThat(this.restTemplate.exchange(
RequestEntity.post(URI.create("http://example.org/body"))
.contentType(MediaType.APPLICATION_XML)
.body("<things><thing><name>RequiredThing</name></thing></things>"),
String.class).getBody()).isEqualTo("Hello Body");
}
@Test
public void postWithRequestThatHasNonMatchingXml() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-equaltoxml.json").build();
ResponseEntity<String> response;
try {
response = this.restTemplate.exchange(
RequestEntity.post(URI.create("http://example.org/body"))
.contentType(MediaType.APPLICATION_XML)
.body("<things><thing><name>AbsentThing</name></thing></things>"),
String.class);
} catch (AssertionError e) {
response = null;
}
if (null != response) {
fail("There was a response for a request that shouldn't be matched : " + response);
}
}
@Test
public void postWithBodyMatchingRegex() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-regex.json").build();
assertThat(this.restTemplate.exchange(
RequestEntity.post(URI.create("http://example.org/body"))
.contentType(MediaType.APPLICATION_XML)
.body("<things><thing><name>RequiredThing</name></thing></things>"),
String.class).getBody()).isEqualTo("Hello Body");
}
@Test
public void postWithRequestThatHasNonMatchingRegex() throws Exception {
WireMockRestServiceServer.with(this.restTemplate) //
.baseUrl("http://example.org") //
.stubs("classpath:/mappings/body-matches-regex.json").build();
ResponseEntity<String> response;
try {
response = this.restTemplate.exchange(
RequestEntity.post(URI.create("http://example.org/body"))
.contentType(MediaType.APPLICATION_XML)
.body("<things><thing><name>AbsentThing</name></thing></things>"),
String.class);
} catch (AssertionError e) {
response = null;
}
if (null != response) {
fail("There was a response for a request that shouldn't be matched : " + response);
}
}
public static class Things {
public List<Thing> things;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Things(List<Thing> things) {
this.things = things;
}
}
public static class Thing {
public String name;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Thing(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request" : {
"urlPath" : "/body",
"method" : "POST",
"bodyPatterns" : [ {
"equalToJson" : "{ \"things\": [ { \"name\" : \"RequiredThing\" } ] }"
} ]
},
"response" : {
"status" : 200,
"body" : "Hello Body"
}
}

View File

@@ -0,0 +1,13 @@
{
"request" : {
"urlPath" : "/body",
"method" : "POST",
"bodyPatterns" : [ {
"equalToXml" : "<things><thing><name>RequiredThing</name></thing></things>"
} ]
},
"response" : {
"status" : 200,
"body" : "Hello Body"
}
}

View File

@@ -0,0 +1,13 @@
{
"request" : {
"urlPath" : "/body",
"method" : "POST",
"bodyPatterns" : [ {
"matchesJsonPath" : "$.things[?(@.name == 'RequiredThing')]"
} ]
},
"response" : {
"status" : 200,
"body" : "Hello Body"
}
}

View File

@@ -0,0 +1,13 @@
{
"request" : {
"urlPath" : "/body",
"method" : "POST",
"bodyPatterns" : [ {
"matches" : ".*RequiredThing.*"
} ]
},
"response" : {
"status" : 200,
"body" : "Hello Body"
}
}

View File

@@ -0,0 +1,13 @@
{
"request" : {
"urlPath" : "/body",
"method" : "POST",
"bodyPatterns" : [ {
"matchesXPath" : "//things"
} ]
},
"response" : {
"status" : 200,
"body" : "Hello Body"
}
}