Merge branch '2.0.x'

This commit is contained in:
Marcin Grzejszczak
2018-12-16 14:08:23 +01:00
11 changed files with 279 additions and 57 deletions

View File

@@ -3,7 +3,7 @@ image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/
image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/{branch}/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"]
:introduction_url: ../../../..
:verifier_core_path: ../../../../spring-cloud-contract-verifier
:verifier_core_path: {introduction_url}/spring-cloud-contract-verifier
== Spring Cloud Contract

View File

@@ -877,3 +877,39 @@ was started will be attached.
Yes! With version 1.2.0 we've added such a possibility. It's enough to call `file(...)` method in the
DSL and provide a path relative to where the contract lays.
If you're using YAML just use the `bodyFromFile` property.
==== Why do I sometimes get `SocketException`
When using an HTTP client (e.g. `RestTemplate`) and running several tests that share a Spring context, you might sometimes get the following exception:
```
java.net.SocketException: Unexpected end of file from server
```
Tom Akehurst, the creator of WireMock did the following analysis of this issue.
> I looked at tcpdump while running the failing test. `HttpUrlConnection` is doing something weird - it's creating a connection in a previous test case, which works fine, then the usual `fin` -> `fin ack` etc. ending handshake happens. But it seems it isn't discarded, but reused after that. Because the server thinks (rightly) that the connection is closed, it just sends a RST packet. Calling the `/__admin` endpoint just happened to remove the dead connection from the pool. This also fixes the problem (which using the Java HTTP client): System.setProperty("http.keepAlive", "false");
There are the ways to solve this problem.
First, just use a different HTTP client for `RestTemplate`. Example for using Apache HTTP client:
.pom.xml
[source,xml,indent=0]
----
include::{standalone_restdocs_path}/http-client/pom.xml[tags=httpclient,indent=0]
----
[source,java,indent=0]
----
include::{standalone_restdocs_path}/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=custom_request_factory,indent=0]
----
Second option is to set the system property. You can set it either in code or pass it to your tests via a plugin.
[source,java,indent=0]
----
static {
System.setProperty("http.keepAlive", "false");
}
----

View File

@@ -1,4 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2018 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -53,6 +70,13 @@
<artifactId>spring-cloud-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<!-- tag::httpclient[] -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>compile</scope>
</dependency>
<!-- end::httpclient[] -->
<dependency>
<groupId>com.example</groupId>
<artifactId>http-server-restdocs</artifactId>

View File

@@ -1,12 +1,21 @@
package com.example.loan;
/*
* Copyright 2013-2018 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.
*
*/
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
package com.example.loan;
import com.example.loan.model.FraudCheckStatus;
import com.example.loan.model.FraudServiceRequest;
@@ -15,6 +24,15 @@ import com.example.loan.model.LoanApplication;
import com.example.loan.model.LoanApplicationResult;
import com.example.loan.model.LoanApplicationStatus;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
@ConfigurationProperties("service")
public class LoanApplicationService {
@@ -28,6 +46,10 @@ public class LoanApplicationService {
public LoanApplicationService() {
this.restTemplate = new RestTemplate();
// tag::custom_request_factory[]
this.restTemplate
.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
// end::custom_request_factory[]
}
public LoanApplicationResult loanApplication(LoanApplication loanApplication) {

View File

@@ -1,3 +1,20 @@
/*
* Copyright 2013-2018 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 com.example.loan;
import java.net.URI;
@@ -8,6 +25,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;

View File

@@ -1,4 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2018 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -20,6 +37,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -1,3 +1,20 @@
/*
* Copyright 2013-2018 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.provider.wiremock;
import java.util.List;
@@ -7,12 +24,13 @@ import java.util.concurrent.ConcurrentHashMap;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.stubrunner.HttpServerStub;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.contract.wiremock.WireMockUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
/**
* Stops the {@link HttpServerStub} after each test class
@@ -30,6 +48,12 @@ public final class StubRunnerWireMockTestExecutionListener
@Override
public void beforeTestClass(TestContext testContext) {
if (testContext.getTestClass().getAnnotationsByType(AutoConfigureStubRunner.class).length == 0) {
if (log.isTraceEnabled()) {
log.trace("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping");
}
return;
}
Map<WireMockHttpServerStub, PortAndMappings> stubs = STUBS
.get(testContext.getApplicationContext());
if (stubs != null) {
@@ -45,34 +69,24 @@ public final class StubRunnerWireMockTestExecutionListener
List<StubMapping> mappings = entry.getValue().mappings;
if (log.isDebugEnabled()) {
log.debug("Stopped a running WireMock instance at " + "port ["
+ entry.getValue().port + "] with stub mappings " + "size ["
+ entry.getValue().port + "] with stub mappings size ["
+ mappings.size() + "]. Restarting the stub.");
}
entry.getKey().start(entry.getValue().port);
entry.getKey().registerDescriptors(mappings);
/*
* Thanks to Tom Akehurst: I looked at tcpdump while running the failing
* test. HttpUrlConnection is doing something weird - it's creating a
* connection in a previous test case, which works fine, then the usual
* fin -> fin ack etc. etc. ending handshake happens. But it seems it
* isn't discarded, but reused after that. Because the server thinks
* (rightly) that the connection is closed, it just sends a RST packet.
* Calling the admin endpoint just happened to remove the dead connection
* from the pool. This also fixes the problem (which using the Java HTTP
* client): System.setProperty("http.keepAlive", "false");
*/
Assert.isTrue(
new RestTemplate()
.getForEntity("http://localhost:" + entry.getValue().port
+ "/__admin/mappings", String.class)
.getStatusCode().is2xxSuccessful(),
"__admin/mappings endpoint wasn't accessible");
WireMockUtils.getMappingsEndpoint(entry.getValue().port);
}
}
}
@Override
public void afterTestClass(TestContext testContext) {
if (testContext.getTestClass().getAnnotationsByType(AutoConfigureStubRunner.class).length == 0) {
if (log.isTraceEnabled()) {
log.trace("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping");
}
return;
}
STUBS.put(testContext.getApplicationContext(), WireMockHttpServerStub.SERVERS);
if (log.isDebugEnabled()) {
log.debug("Stopping servers " + WireMockHttpServerStub.SERVERS);
@@ -81,5 +95,4 @@ public final class StubRunnerWireMockTestExecutionListener
serverStub.stop();
}
}
}

View File

@@ -1,3 +1,20 @@
/*
* Copyright 2013-2018 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.provider.wiremock;
import java.io.File;
@@ -20,6 +37,8 @@ import com.github.tomakehurst.wiremock.extension.Extension;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import wiremock.com.github.jknack.handlebars.Helper;
import org.springframework.cloud.contract.stubrunner.HttpServerStub;
import org.springframework.cloud.contract.verifier.builder.handlebars.HandlebarsEscapeHelper;
import org.springframework.cloud.contract.verifier.builder.handlebars.HandlebarsJsonPathHelper;
@@ -31,7 +50,6 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.SocketUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import wiremock.com.github.jknack.handlebars.Helper;
/**
* Abstraction over WireMock as a HTTP Server Stub
@@ -115,7 +133,7 @@ public class WireMockHttpServerStub implements HttpServerStub {
log.debug("Started WireMock at port [" + port + "]");
}
if (!SERVERS.containsKey(this)) {
SERVERS.put(this, new PortAndMappings(port, new ArrayList<StubMapping>()));
SERVERS.put(this, new PortAndMappings(port, new ArrayList<>()));
}
return this;
}

View File

@@ -1,17 +1,18 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2013-2018 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
* 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.wiremock;
@@ -20,6 +21,7 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -28,6 +30,7 @@ import com.github.tomakehurst.wiremock.common.Slf4jNotifier;
import com.github.tomakehurst.wiremock.core.Options;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -38,10 +41,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
/**
* Configuration and lifecycle for a Spring Application context that wants to run a
@@ -84,8 +85,7 @@ public class WireMockConfiguration implements SmartLifecycle {
@PostConstruct
public void init() throws IOException {
if (this.options == null) {
com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = WireMockSpring
.options();
com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = WireMockSpring.options();
if (this.wireMock.getServer().getPort() != 8080) {
factory.port(this.wireMock.getServer().getPort());
}
@@ -105,7 +105,7 @@ public class WireMockConfiguration implements SmartLifecycle {
registerStubs();
if (log.isDebugEnabled()) {
log.debug("WireMock server has [" + this.server.getStubMappings().size()
+ "] registered");
+ "] stubs registered");
}
if (!this.beanFactory.containsBean(WIREMOCK_SERVER_BEAN_NAME)) {
this.beanFactory.registerSingleton(WIREMOCK_SERVER_BEAN_NAME, this.server);
@@ -133,6 +133,10 @@ public class WireMockConfiguration implements SmartLifecycle {
}
}
int port() {
return this.server.port();
}
void reset() {
this.server.resetAll();
}
@@ -168,28 +172,14 @@ public class WireMockConfiguration implements SmartLifecycle {
log.debug("Started WireMock at port [" + this.server.port() + "]. It has ["
+ this.server.getStubMappings().size() + "] mappings registered");
}
/*
* Thanks to Tom Akehurst: I looked at tcpdump while running the failing test.
* HttpUrlConnection is doing something weird - it's creating a connection in a
* previous test case, which works fine, then the usual fin -> fin ack etc. etc.
* ending handshake happens. But it seems it isn't discarded, but reused after
* that. Because the server thinks (rightly) that the connection is closed, it
* just sends a RST packet. Calling the admin endpoint just happened to remove the
* dead connection from the pool. This also fixes the problem (which using the
* Java HTTP client): System.setProperty("http.keepAlive", "false");
*/
Assert.isTrue(
new RestTemplate()
.getForEntity("http://localhost:" + this.server.port()
+ "/__admin/mappings", String.class)
.getStatusCode().is2xxSuccessful(),
"__admin/mappings endpoint wasn't accessible");
WireMockUtils.getMappingsEndpoint(this.port());
}
@Override
public void stop() {
if (this.running) {
this.server.stop();
reset();
this.server.shutdownServer();
this.running = false;
if (log.isDebugEnabled()) {
log.debug("Stopped WireMock instance");

View File

@@ -1,7 +1,25 @@
/*
* Copyright 2013-2018 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.wiremock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
@@ -28,9 +46,9 @@ public final class WireMockTestExecutionListener extends AbstractTestExecutionLi
+ wireMockConfiguration.isRunning() + "]");
}
if (!wireMockConfiguration.isRunning()) {
wireMockConfiguration.reset();
wireMockConfiguration.init();
wireMockConfiguration.start();
WireMockUtils.getMappingsEndpoint(wireMockConfiguration.port());
}
}
catch (Exception e) {

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2013-2018 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.wiremock;
import java.io.IOException;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.util.Assert;
/**
* Utility class to work with WireMock.
*
* @author Marcin Grzejszczak
* @since 2.0.3
*/
public final class WireMockUtils {
private WireMockUtils() {
throw new IllegalStateException("Don't instantiate");
}
/**
* Thanks to Tom Akehurst: I looked at tcpdump while running the failing
* test. HttpUrlConnection is doing something weird - it's creating a
* connection in a previous test case, which works fine, then the usual
* fin -> fin ack etc. etc. ending handshake happens. But it seems it
* isn't discarded, but reused after that. Because the server thinks
* (rightly) that the connection is closed, it just sends a RST packet.
* Calling the admin endpoint just happened to remove the dead connection
* from the pool. This also fixes the problem (which using the Java HTTP
* client): System.setProperty("http.keepAlive", "false");
**/
public static CloseableHttpResponse getMappingsEndpoint(int port) {
CloseableHttpClient client = HttpClientBuilder.create().build();
try {
CloseableHttpResponse response = client
.execute(new HttpHost("localhost", port), new HttpGet("/__admin/mappings"));
Assert.isTrue(response.getStatusLine().getStatusCode() == 200, "Status code must be 200");
return response;
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}