diff --git a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc index 087d19afe2..8e80571029 100644 --- a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc +++ b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc @@ -94,6 +94,49 @@ include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.j The use `@ClassRule` means that the server will shut down after all the methods in this class. +== Relaxed SSL Validation for Rest Template + +WireMock allows you to stub a "secure" server with an "https" URL protocol. If your application wants to +contact that stub server in an integration test, then it will find that the SSL certificates are not +valid (it's the usual problem with self-installed certificates). The best option is often to just +re-configure the client to use "http", but if that's not open to you then you can ask Spring to configure +an HTTP client that ignores SSL validation errors (just for tests). + +To make this work with minimum fuss you need to be using the Spring Boot `RestTemplateBuilder` in your app, +e.g. + +[source,java,indent=0] +---- + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } +---- + +This is because the builder is passed through callbacks to initalize it, so the SSL validation can be set up +in the client at that point. This will happen automatically in your test if you are using the +`@AutoConfigureWireMock` annotation (or the stub runner). If you are using the JUnit `@Rule` approach you need +to add the `@AutoConfigureHttpClient` annotation as well: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest("app.baseUrl=https://localhost:6443") +@AutoConfigureHttpClient +public class WiremockHttpsServerApplicationTests { + + @ClassRule + public static WireMockClassRule wiremock = new WireMockClassRule( + WireMockSpring.options().httpsPort(6443)); +... +} +---- + +If you are using `spring-boot-starter-test` then you will have the Apache HTTP client on the classpath and it will +be selected by the `RestTemplateBuilder` and configured to ignore SSL errors. If you are using the default `java.net` +client you don't need the annotation (but it won't do any harm). There is no support currently for other clients, but +it may be added in future releases. + == WireMock and Spring MVC Mocks Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a diff --git a/pom.xml b/pom.xml index 67b3b4604e..f5165f2022 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-build - 1.3.1.BUILD-SNAPSHOT + 1.3.2.BUILD-SNAPSHOT @@ -27,12 +27,12 @@ 2.17.0 1.5.3.BUILD-SNAPSHOT 2.17 - 1.3.1.BUILD-SNAPSHOT - 1.1.0.BUILD-SNAPSHOT + 1.3.2.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT Chelsea.BUILD-SNAPSHOT - 1.3.0.BUILD-SNAPSHOT - 1.2.0.BUILD-SNAPSHOT - 1.2.0.BUILD-SNAPSHOT + 1.3.1.BUILD-SNAPSHOT + 1.2.1.BUILD-SNAPSHOT + 1.2.1.BUILD-SNAPSHOT diff --git a/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java b/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java index c91df67aea..cf26b27f35 100644 --- a/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java +++ b/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java @@ -5,6 +5,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -20,9 +21,6 @@ import com.example.loan.model.Response; @Service public class LoanApplicationService { - private static final String FRAUD_SERVICE_JSON_VERSION_1 = - "application/vnd.fraud.v1+json"; - private final RestTemplate restTemplate; private int port = 6565; @@ -45,7 +43,7 @@ public class LoanApplicationService { private FraudServiceResponse sendRequestToFraudDetectionService( FraudServiceRequest request) { HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1); + httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); // tag::client_call_server[] ResponseEntity response = @@ -70,7 +68,7 @@ public class LoanApplicationService { public int countAllFrauds() { HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1); + httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); ResponseEntity response = restTemplate.exchange("http://localhost:" + port + "/frauds", HttpMethod.GET, new HttpEntity<>(httpHeaders), @@ -80,7 +78,7 @@ public class LoanApplicationService { public int countDrunks() { HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1); + httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); ResponseEntity response = restTemplate.exchange("http://localhost:" + port + "/drunks", HttpMethod.GET, new HttpEntity<>(httpHeaders), diff --git a/samples/standalone/dsl/http-server/pom.xml b/samples/standalone/dsl/http-server/pom.xml index e0a6490c58..772176997a 100644 --- a/samples/standalone/dsl/http-server/pom.xml +++ b/samples/standalone/dsl/http-server/pom.xml @@ -117,7 +117,7 @@ org.springframework.cloud spring-cloud-contract-maven-plugin - [1.1.0.BUILD-SNAPSHOT,) + [1.1.1.BUILD-SNAPSHOT,) convert generateTests diff --git a/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java b/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java index d172969868..764f8a6e96 100644 --- a/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java +++ b/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java @@ -15,17 +15,12 @@ import static org.springframework.web.bind.annotation.RequestMethod.PUT; @RestController public class FraudDetectionController { - private static final String FRAUD_SERVICE_JSON_VERSION_1 = "application/vnd.fraud.v1+json"; private static final String NO_REASON = null; private static final String AMOUNT_TOO_HIGH = "Amount too high"; private static final BigDecimal MAX_AMOUNT = new BigDecimal("5000"); // tag::server_api[] - @RequestMapping( - value = "/fraudcheck", - method = PUT, - consumes = FRAUD_SERVICE_JSON_VERSION_1, - produces = FRAUD_SERVICE_JSON_VERSION_1) + @RequestMapping(value = "/fraudcheck", method = PUT) public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) { // end::server_api[] // tag::new_impl[] diff --git a/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudNameController.java b/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudNameController.java index ea6ff8f2c0..4ba6881594 100644 --- a/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudNameController.java +++ b/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudNameController.java @@ -10,17 +10,13 @@ import org.springframework.web.bind.annotation.RestController; @RestController class FraudNameController { - private static final String FRAUD_SERVICE_JSON_VERSION_1 = "application/vnd.fraud.v1+json"; - private final FraudVerifier fraudVerifier; FraudNameController(FraudVerifier fraudVerifier) { this.fraudVerifier = fraudVerifier; } - @PutMapping( - value = "/frauds/name", - produces = FRAUD_SERVICE_JSON_VERSION_1) + @PutMapping(value = "/frauds/name") public NameResponse checkByName(@RequestBody NameRequest request) { boolean fraud = this.fraudVerifier.isFraudByName(request.getName()); if (fraud) { diff --git a/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudStatsController.java b/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudStatsController.java index ab8d933814..ac9340611c 100644 --- a/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudStatsController.java +++ b/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudStatsController.java @@ -6,24 +6,18 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class FraudStatsController { - private static final String FRAUD_SERVICE_JSON_VERSION_1 = "application/vnd.fraud.v1+json"; - private final StatsProvider statsProvider; public FraudStatsController(StatsProvider statsProvider) { this.statsProvider = statsProvider; } - @GetMapping( - value = "/frauds", - produces = FRAUD_SERVICE_JSON_VERSION_1) + @GetMapping(value = "/frauds") public Response countAllFrauds() { return new Response(this.statsProvider.count(FraudType.ALL)); } - @GetMapping( - value = "/drunks", - produces = FRAUD_SERVICE_JSON_VERSION_1) + @GetMapping(value = "/drunks") public Response countAllDrunks() { return new Response(this.statsProvider.count(FraudType.DRUNKS)); } diff --git a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy index 02614d1555..64dcdc139c 100644 --- a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy +++ b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy @@ -9,7 +9,7 @@ org.springframework.cloud.contract.spec.Contract.make { loanAmount: 99999 ]) headers { // (5) - contentType('application/vnd.fraud.v1+json') + contentType('application/json') } } response { // (6) @@ -19,7 +19,7 @@ org.springframework.cloud.contract.spec.Contract.make { rejectionReason: "Amount too high" ]) headers { // (9) - contentType('application/vnd.fraud.v1+json') + contentType('application/json') } } } @@ -33,12 +33,12 @@ From the Consumer perspective, when shooting a request in the integration test: (4) - with the JSON body that * has a field `clientId` that matches a regular expression `[0-9]{10}` * has a field `loanAmount` that is equal to `99999` -(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json` +(5) - with header `Content-Type` equal to `application/json` (6) - then the response will be sent with (7) - status equal `200` (8) - and JSON body equal to { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" } -(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json` +(9) - with header `Content-Type` equal to `application/json` From the Producer perspective, in the autogenerated producer-side test: @@ -48,10 +48,10 @@ From the Producer perspective, in the autogenerated producer-side test: (4) - with the JSON body that * has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}` * has a field `loanAmount` that is equal to `99999` -(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json` +(5) - with header `Content-Type` equal to `application/json` (6) - then the test will assert if the response has been sent with (7) - status equal `200` (8) - and JSON body equal to { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" } -(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*` +(9) - with header `Content-Type` matching `application/json.*` */ \ No newline at end of file diff --git a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.groovy b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.groovy index c3dce3f32d..a04fe8447f 100644 --- a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.groovy +++ b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.groovy @@ -12,7 +12,7 @@ org.springframework.cloud.contract.spec.Contract.make { """ ) headers { - contentType("application/vnd.fraud.v1+json") + contentType("application/json") } } @@ -23,7 +23,7 @@ org.springframework.cloud.contract.spec.Contract.make { rejectionReason: $(consumer(null), producer(execute('assertThatRejectionReasonIsNull($it)'))) ) headers { - contentType("application/vnd.fraud.v1+json") + contentType("application/json") } } diff --git a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy index a6ed5a97d7..1b95f5e19f 100644 --- a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy +++ b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.groovy @@ -15,7 +15,7 @@ import org.springframework.cloud.contract.spec.Contract count: 200 ]) headers { - contentType("application/vnd.fraud.v1+json") + contentType("application/json") } } }, @@ -30,7 +30,7 @@ import org.springframework.cloud.contract.spec.Contract count: 100 ]) headers { - contentType("application/vnd.fraud.v1+json") + contentType("application/json") } } } diff --git a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.groovy b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.groovy index f2f96c2676..31c3d0e215 100644 --- a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.groovy +++ b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnAFraudForTheName.groovy @@ -10,7 +10,7 @@ org.springframework.cloud.contract.spec.Contract.make { name: "fraud" ]) headers { - contentType("application/vnd.fraud.v1+json") + contentType("application/json") } } response { diff --git a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.groovy b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.groovy index eddea317a7..ce9a92a533 100644 --- a/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.groovy +++ b/samples/standalone/dsl/http-server/src/test/resources/contracts/fraudname/shouldReturnNonFraudForTheName.groovy @@ -8,7 +8,7 @@ org.springframework.cloud.contract.spec.Contract.make { name: $(anyAlphaUnicode()) ]) headers { - contentType("application/vnd.fraud.v1+json") + contentType("application/json") } } response { diff --git a/samples/standalone/dsl/pom.xml b/samples/standalone/dsl/pom.xml index 13c73b511d..1246a5d6d3 100644 --- a/samples/standalone/dsl/pom.xml +++ b/samples/standalone/dsl/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-contract-samples-standalone - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT .. diff --git a/samples/standalone/messaging/pom.xml b/samples/standalone/messaging/pom.xml index 8893959be8..05616bbc2c 100644 --- a/samples/standalone/messaging/pom.xml +++ b/samples/standalone/messaging/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-contract-samples-standalone - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT .. diff --git a/samples/standalone/messaging/stream-source/pom.xml b/samples/standalone/messaging/stream-source/pom.xml index f11d74d600..e11c1616e0 100644 --- a/samples/standalone/messaging/stream-source/pom.xml +++ b/samples/standalone/messaging/stream-source/pom.xml @@ -156,7 +156,7 @@ org.springframework.cloud spring-cloud-contract-maven-plugin - [1.1.0.BUILD-SNAPSHOT,) + [1.1.1.BUILD-SNAPSHOT,) convert generateTests diff --git a/samples/standalone/pact/pact-http-server/pom.xml b/samples/standalone/pact/pact-http-server/pom.xml index 310125e090..13eb8c236a 100644 --- a/samples/standalone/pact/pact-http-server/pom.xml +++ b/samples/standalone/pact/pact-http-server/pom.xml @@ -125,7 +125,7 @@ org.springframework.cloud spring-cloud-contract-maven-plugin - [1.1.0.BUILD-SNAPSHOT,) + [1.1.1.BUILD-SNAPSHOT,) convert generateTests diff --git a/samples/standalone/pact/pom.xml b/samples/standalone/pact/pom.xml index 3b582cc2b7..56469b3a2b 100644 --- a/samples/standalone/pact/pom.xml +++ b/samples/standalone/pact/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-contract-samples-standalone - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT .. diff --git a/samples/standalone/pom.xml b/samples/standalone/pom.xml index 56f1211331..85fb0941cc 100644 --- a/samples/standalone/pom.xml +++ b/samples/standalone/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-contract-samples - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT .. diff --git a/samples/standalone/restdocs/pom.xml b/samples/standalone/restdocs/pom.xml index 45d64b19f8..bf1aad247f 100644 --- a/samples/standalone/restdocs/pom.xml +++ b/samples/standalone/restdocs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-contract-samples-standalone - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT .. diff --git a/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java b/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java index dbf42f24cc..0608f2f338 100644 --- a/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java +++ b/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java @@ -3,6 +3,7 @@ package com.example; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,8 +14,8 @@ import org.springframework.web.client.RestTemplate; public class WiremockTestsApplication { @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); } public static void main(String[] args) { diff --git a/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java b/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java index e700ce0211..93b83c12d5 100644 --- a/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java +++ b/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java @@ -1,26 +1,28 @@ package com.example; +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureHttpClient; +import org.springframework.cloud.contract.wiremock.WireMockSpring; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.contract.wiremock.WireMockSpring; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; - -import com.github.tomakehurst.wiremock.junit.WireMockClassRule; - - @RunWith(SpringRunner.class) @SpringBootTest("app.baseUrl=https://localhost:8443") @DirtiesContext +@AutoConfigureHttpClient public class WiremockHttpsServerApplicationTests { @ClassRule @@ -32,8 +34,8 @@ public class WiremockHttpsServerApplicationTests { @Test public void contextLoads() throws Exception { - stubFor(get(urlEqualTo("/resource")) - .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + stubFor(get(urlEqualTo("/resource")).willReturn(aResponse() + .withHeader("Content-Type", "text/plain").withBody("Hello World!"))); assertThat(this.service.go()).isEqualTo("Hello World!"); } diff --git a/samples/wiremock-jetty/src/test/java/com/example/WiremockServerApplicationTests.java b/samples/wiremock-jetty/src/test/java/com/example/WiremockServerApplicationTests.java index 6c6f1e6da9..9bbb1671d6 100644 --- a/samples/wiremock-jetty/src/test/java/com/example/WiremockServerApplicationTests.java +++ b/samples/wiremock-jetty/src/test/java/com/example/WiremockServerApplicationTests.java @@ -1,14 +1,17 @@ package com.example; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.assertj.core.api.Assertions.assertThat; +import com.github.tomakehurst.wiremock.http.Fault; +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import org.apache.http.MalformedChunkCodingException; +import org.apache.http.NoHttpResponseException; +import org.apache.http.client.ClientProtocolException; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -16,7 +19,12 @@ import org.springframework.cloud.contract.wiremock.WireMockSpring; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.instanceOf; @RunWith(SpringRunner.class) @SpringBootTest(properties="app.baseUrl=http://localhost:6061", webEnvironment=WebEnvironment.NONE) @@ -26,14 +34,41 @@ public class WiremockServerApplicationTests { @ClassRule public static WireMockClassRule wiremock = new WireMockClassRule(WireMockSpring.options().port(6061)); + @Rule + public ExpectedException expected = ExpectedException.none(); + @Autowired private Service service; @Test - public void contextLoads() throws Exception { + public void hello() throws Exception { stubFor(get(urlEqualTo("/resource")) .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); assertThat(this.service.go()).isEqualTo("Hello World!"); } + @Test + public void randomData() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withFault(Fault.RANDOM_DATA_THEN_CLOSE))); + expected.expectCause(instanceOf(ClientProtocolException.class)); + assertThat(this.service.go()).isEqualTo("Oops!"); + } + + @Test + public void emptyResponse() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE))); + expected.expectCause(instanceOf(NoHttpResponseException.class)); + assertThat(this.service.go()).isEqualTo("Oops!"); + } + + @Test + public void malformed() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK))); + expected.expectCause(instanceOf(MalformedChunkCodingException.class)); + assertThat(this.service.go()).isEqualTo("Oops!"); + } + } diff --git a/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java b/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java index fa59698f17..926417a038 100644 --- a/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java +++ b/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java @@ -3,6 +3,7 @@ package com.example; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,8 +14,8 @@ import org.springframework.web.client.RestTemplate; public class WiremockTestsApplication { @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); } public static void main(String[] args) { diff --git a/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java b/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java index add1d311fe..9e30002fdf 100644 --- a/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java +++ b/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java @@ -11,6 +11,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureHttpClient; import org.springframework.cloud.contract.wiremock.WireMockSpring; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; @@ -21,6 +22,7 @@ import com.github.tomakehurst.wiremock.junit.WireMockClassRule; @RunWith(SpringRunner.class) @SpringBootTest("app.baseUrl=https://localhost:6443") @DirtiesContext +@AutoConfigureHttpClient public class WiremockHttpsServerApplicationTests { @ClassRule diff --git a/samples/wiremock-tomcat/src/test/java/com/example/WiremockServerApplicationTests.java b/samples/wiremock-tomcat/src/test/java/com/example/WiremockServerApplicationTests.java index d7f2980b6f..d4c5656527 100644 --- a/samples/wiremock-tomcat/src/test/java/com/example/WiremockServerApplicationTests.java +++ b/samples/wiremock-tomcat/src/test/java/com/example/WiremockServerApplicationTests.java @@ -1,14 +1,18 @@ package com.example; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; +import com.github.tomakehurst.wiremock.http.Fault; +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; + +import org.apache.http.NoHttpResponseException; +import org.apache.http.client.ClientProtocolException; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -16,7 +20,12 @@ import org.springframework.cloud.contract.wiremock.WireMockSpring; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.instanceOf; @RunWith(SpringRunner.class) @SpringBootTest(properties="app.baseUrl=http://localhost:6067", webEnvironment=WebEnvironment.NONE) @@ -26,6 +35,9 @@ public class WiremockServerApplicationTests { @ClassRule public static WireMockClassRule wiremock = new WireMockClassRule(WireMockSpring.options().port(6067)); + @Rule + public ExpectedException expected = ExpectedException.none(); + @Autowired private Service service; @@ -36,4 +48,30 @@ public class WiremockServerApplicationTests { assertThat(this.service.go()).isEqualTo("Hello World!"); } + @Test + public void randomData() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withFault(Fault.RANDOM_DATA_THEN_CLOSE))); + expected.expectCause(instanceOf(ClientProtocolException.class)); + assertThat(this.service.go()).isEqualTo("Oops!"); + } + + @Test + public void emptyResponse() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE))); + expected.expectCause(instanceOf(NoHttpResponseException.class)); + assertThat(this.service.go()).isEqualTo("Oops!"); + } + + @Test + public void malformed() throws Exception { + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK))); + // It's a different exception type than Jetty, but it's in the right ballpark + expected.expectCause(instanceOf(IOException.class)); + expected.expectMessage("chunk"); + assertThat(this.service.go()).isEqualTo("Oops!"); + } + } diff --git a/spring-cloud-contract-dependencies/pom.xml b/spring-cloud-contract-dependencies/pom.xml index ea304b54f1..393d99b475 100644 --- a/spring-cloud-contract-dependencies/pom.xml +++ b/spring-cloud-contract-dependencies/pom.xml @@ -5,7 +5,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.3.1.BUILD-SNAPSHOT + 1.3.2.BUILD-SNAPSHOT spring-cloud-contract-dependencies diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle index 1c4613a77b..dc8a6bca6f 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/build.gradle @@ -23,7 +23,7 @@ ext { ] } -project.version = findProperty('verifierVersion') ?: '1.1.0.BUILD-SNAPSHOT' +project.version = findProperty('verifierVersion') ?: '1.1.1.BUILD-SNAPSHOT' apply plugin: 'groovy' apply from: "$rootDir/gradle/release.gradle" apply plugin: 'eclipse' diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/gradle.properties b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/gradle.properties index dc488d8fa7..1c3fb93350 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/gradle.properties +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/gradle.properties @@ -16,5 +16,5 @@ nexusUsername = nexusPassword = -verifierVersion=1.1.0.BUILD-SNAPSHOT +verifierVersion=1.1.1.BUILD-SNAPSHOT org.gradle.daemon=false \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/gradle.properties b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/gradle.properties index 5b12dfc27a..ef921a31eb 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/gradle.properties +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/gradle.properties @@ -15,5 +15,5 @@ # wiremockVersion=2.5.1 jsonAssertVersion=0.4.8 -verifierVersion=1.1.0.BUILD-SNAPSHOT +verifierVersion=1.1.1.BUILD-SNAPSHOT diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/gradle.properties b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/gradle.properties index 71b597b876..6f9a459444 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/gradle.properties +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/gradle.properties @@ -15,4 +15,4 @@ # wiremockVersion=2.5.1 jsonAssertVersion=0.4.8 -verifierVersion=1.1.0.BUILD-SNAPSHOT +verifierVersion=1.1.1.BUILD-SNAPSHOT diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/gradle.properties b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/gradle.properties index c6339b65ea..33ab5a5b1c 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/gradle.properties +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/gradle.properties @@ -15,4 +15,4 @@ # wiremockVersion=2.5.1 jsonAssertVersion=0.4.8 -verifierVersion=1.1.0.BUILD-SNAPSHOT \ No newline at end of file +verifierVersion=1.1.1.BUILD-SNAPSHOT \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/gradle.properties b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/gradle.properties index 5b12dfc27a..ef921a31eb 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/gradle.properties +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/gradle.properties @@ -15,5 +15,5 @@ # wiremockVersion=2.5.1 jsonAssertVersion=0.4.8 -verifierVersion=1.1.0.BUILD-SNAPSHOT +verifierVersion=1.1.1.BUILD-SNAPSHOT diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml index 5f68ede9cf..50f3621a39 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml @@ -30,7 +30,7 @@ org.springframework.cloud spring-cloud-contract-maven-plugin - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT com/example/server diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml index 297e4e005e..b4e5c89df7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml @@ -26,7 +26,7 @@ 0.1 - 1.1.0.BUILD-SNAPSHOT + 1.1.1.BUILD-SNAPSHOT diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BlockBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BlockBuilder.groovy index de3f392561..2f2ef6da5c 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BlockBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BlockBuilder.groovy @@ -27,7 +27,6 @@ import groovy.transform.PackageScope * * @since 1.0.0 */ -@PackageScope @CompileStatic class BlockBuilder { @@ -43,21 +42,33 @@ class BlockBuilder { builder = new StringBuilder() } + /** + * Adds indents to start a new block + */ BlockBuilder startBlock() { indents++ return this } + /** + * Ends block by removing indents + */ BlockBuilder endBlock() { indents-- return this } + /** + * Creates a block and adds indents + */ BlockBuilder indent() { startBlock().startBlock() return this } + /** + * Removes indents and closes the block + */ BlockBuilder unindent() { endBlock().endBlock() return this @@ -81,6 +92,7 @@ class BlockBuilder { } } + @PackageScope BlockBuilder addBlock(MethodBuilder methodBuilder) { startBlock() methodBuilder.appendTo(this) @@ -89,6 +101,11 @@ class BlockBuilder { return this } + /** + * Adds the given text at the end of the line + * + * @return updated BlockBuilder + */ BlockBuilder addAtTheEnd(String toAdd) { String lastChar = builder.charAt(builder.length() - 1) as String String secondLastChar = builder.length() >= 2 ? builder.charAt(builder.length() - 2) as String : "" @@ -114,6 +131,12 @@ class BlockBuilder { return character == "{" || character == toAdd } + /** + * Updates the current text with the provided one + * + * @param contents - text to replace the current content with + * @return updated Block Builder + */ BlockBuilder updateContents(String contents) { this.builder.replace(0, this.builder.length(), contents) return this diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureHttpClient.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureHttpClient.java new file mode 100644 index 0000000000..48f3e9d60b --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureHttpClient.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2015 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.context.annotation.Import; + +/** + * Annotation for test classes that want to install a RestTemplateCustomizer that sets up + * a Spring Boot app to ignore SSL errors. Use only in tests! + * + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(WireMockRestTemplateConfiguration.class) +@PropertyMapping("wiremock.server") +public @interface AutoConfigureHttpClient { +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java index fb8db93eb1..b1337ba0c3 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java @@ -39,6 +39,7 @@ import org.springframework.context.annotation.Import; @Documented @Import(WireMockConfiguration.class) @PropertyMapping("wiremock.server") +@AutoConfigureHttpClient public @interface AutoConfigureWireMock { int port() default 8080; diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/JettyFaultInjector.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/JettyFaultInjector.java new file mode 100644 index 0000000000..ab3e2822b8 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/JettyFaultInjector.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016-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.wiremock; + +import java.io.IOException; +import java.nio.channels.ByteChannel; + +import javax.servlet.http.HttpServletResponse; + +import com.github.tomakehurst.wiremock.core.FaultInjector; +import com.google.common.base.Charsets; + +import org.eclipse.jetty.io.ChannelEndPoint; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.BufferUtil; + +import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static com.github.tomakehurst.wiremock.jetty9.JettyUtils.unwrapResponse; + +/** + * @author Dave Syer + * + */ +public class JettyFaultInjector implements FaultInjector { + + private static final byte[] GARBAGE = "lskdu018973t09sylgasjkfg1][]'./.sdlv" + .getBytes(Charsets.UTF_8); + + private final Response response; + private final ByteChannel socket; + + public JettyFaultInjector(HttpServletResponse response) { + this.response = unwrapResponse(response); + this.socket = socket(); + } + + @Override + public void emptyResponseAndCloseConnection() { + try { + this.socket.close(); + } + catch (IOException e) { + throwUnchecked(e); + } + } + + @Override + public void malformedResponseChunk() { + try { + this.response.setStatus(200); + this.response.flushBuffer(); + this.socket.write(BufferUtil.toBuffer(GARBAGE)); + this.socket.close(); + } + catch (IOException e) { + throwUnchecked(e); + } + + } + + @Override + public void randomDataAndCloseConnection() { + try { + this.socket.write(BufferUtil.toBuffer(GARBAGE)); + this.socket.close(); + } + catch (IOException e) { + throwUnchecked(e); + } + } + + private ByteChannel socket() { + HttpChannel httpChannel = this.response.getHttpOutput().getHttpChannel(); + ChannelEndPoint ep = (ChannelEndPoint) httpChannel.getEndPoint(); + return ep.getChannel(); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/JettyFaultInjectorFactory.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/JettyFaultInjectorFactory.java new file mode 100644 index 0000000000..00ebaa6c3b --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/JettyFaultInjectorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-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.wiremock; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.github.tomakehurst.wiremock.core.FaultInjector; +import com.github.tomakehurst.wiremock.servlet.FaultInjectorFactory; + +/** + * @author Dave Syer + * + */ +public class JettyFaultInjectorFactory implements FaultInjectorFactory { + + @Override + public FaultInjector buildFaultInjector(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse) { + return new JettyFaultInjector(httpServletResponse); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java index ecf36be8cb..03d655b972 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/SpringBootHttpServerFactory.java @@ -18,11 +18,27 @@ package org.springframework.cloud.contract.wiremock; import javax.servlet.ServletContext; +import io.undertow.Undertow.Builder; + +import com.github.tomakehurst.wiremock.common.HttpsSettings; +import com.github.tomakehurst.wiremock.common.Notifier; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.core.WireMockApp; +import com.github.tomakehurst.wiremock.http.AdminRequestHandler; +import com.github.tomakehurst.wiremock.http.HttpServer; +import com.github.tomakehurst.wiremock.http.HttpServerFactory; +import com.github.tomakehurst.wiremock.http.RequestHandler; +import com.github.tomakehurst.wiremock.http.StubRequestHandler; +import com.github.tomakehurst.wiremock.servlet.FaultInjectorFactory; +import com.github.tomakehurst.wiremock.servlet.NoFaultInjectorFactory; +import com.github.tomakehurst.wiremock.servlet.WireMockHandlerDispatchingServlet; + import org.apache.catalina.connector.Connector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -64,19 +80,6 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.context.ServletContextAware; -import com.github.tomakehurst.wiremock.common.HttpsSettings; -import com.github.tomakehurst.wiremock.common.Notifier; -import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.core.WireMockApp; -import com.github.tomakehurst.wiremock.http.AdminRequestHandler; -import com.github.tomakehurst.wiremock.http.HttpServer; -import com.github.tomakehurst.wiremock.http.HttpServerFactory; -import com.github.tomakehurst.wiremock.http.RequestHandler; -import com.github.tomakehurst.wiremock.http.StubRequestHandler; -import com.github.tomakehurst.wiremock.servlet.WireMockHandlerDispatchingServlet; - -import io.undertow.Undertow.Builder; - /** * @author Dave Syer * @@ -258,6 +261,8 @@ class WiremockServerConfiguration { @Autowired private StubRequestHandler stubRequestHandler; @Autowired + private FaultInjectorFactory faultInjectorFactory; + @Autowired private Options options; @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @@ -265,6 +270,10 @@ class WiremockServerConfiguration { ServletRegistrationBean reg = new ServletRegistrationBean(); reg.addInitParameter(RequestHandler.HANDLER_CLASS_KEY, StubRequestHandler.class.getName()); + if (WiremockServerConfiguration.this.faultInjectorFactory != null) { + reg.addInitParameter(FaultInjectorFactory.INJECTOR_CLASS_KEY, + FaultInjectorFactory.class.getName()); + } reg.setServlet(new WireMockHandlerDispatchingServlet()); reg.setName("stub"); reg.addUrlMappings("/"); @@ -293,6 +302,10 @@ class WiremockServerConfiguration { WiremockServerConfiguration.this.stubRequestHandler); servletContext.setAttribute(Notifier.KEY, WiremockServerConfiguration.this.options.notifier()); + if (WiremockServerConfiguration.this.faultInjectorFactory != null) { + servletContext.setAttribute(FaultInjectorFactory.class.getName(), + WiremockServerConfiguration.this.faultInjectorFactory); + } } }; } @@ -367,6 +380,11 @@ class ContainerConfiguration { return tomcat; } + @Bean + public FaultInjectorFactory faultInjectorFactory() { + return new TomcatFaultInjectorFactory(); + } + @EventListener public void serverUp(EmbeddedServletContainerInitializedEvent event) { if (this.connector != null) { @@ -417,6 +435,11 @@ class ContainerConfiguration { return undertow; } + @Bean + public FaultInjectorFactory faultInjectorFactory() { + return new NoFaultInjectorFactory(); + } + @EventListener public void serverUp(EmbeddedServletContainerInitializedEvent event) { if (this.port != null) { @@ -457,6 +480,11 @@ class ContainerConfiguration { return jetty; } + @Bean + public JettyFaultInjectorFactory faultInjectorFactory() { + return new JettyFaultInjectorFactory(); + } + private org.eclipse.jetty.server.Connector createStandardConnector( Server server) { ServerConnector connector = new ServerConnector(server, -1, -1); diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/TomcatFaultInjector.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/TomcatFaultInjector.java new file mode 100644 index 0000000000..0abf792f85 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/TomcatFaultInjector.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016-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.wiremock; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; + +import javax.servlet.http.HttpServletResponse; + +import com.github.tomakehurst.wiremock.common.Exceptions; +import com.github.tomakehurst.wiremock.core.FaultInjector; +import com.google.common.base.Charsets; + +import org.apache.coyote.Response; +import org.apache.tomcat.util.net.SocketWrapperBase; + +import org.springframework.util.ReflectionUtils; + +import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; + +/** + * @author Dave Syer + * + */ +public class TomcatFaultInjector implements FaultInjector { + + private static final byte[] GARBAGE = "lskdu018973t09sylgasjkfg1][]'./.sdlv" + .getBytes(Charsets.UTF_8); + private final Response response; + private SocketWrapperBase socket; + + public TomcatFaultInjector(HttpServletResponse response) { + this.response = ((org.apache.catalina.connector.Response) getField(response, + "response")).getCoyoteResponse(); + this.socket = (SocketWrapperBase) getField( + getField(this.response, "outputBuffer"), "socketWrapper"); + } + + private Object getField(Object target, String string) { + Field field = ReflectionUtils.findField(target.getClass(), string); + ReflectionUtils.makeAccessible(field); + return ReflectionUtils.getField(field, target); + } + + @Override + public void emptyResponseAndCloseConnection() { + try { + this.socket.close(); + } + catch (IOException e) { + Exceptions.throwUnchecked(e); + } + } + + @Override + public void malformedResponseChunk() { + try { + this.response.sendHeaders(); + this.response.doWrite(ByteBuffer.wrap(GARBAGE)); + this.socket.flush(true); + this.socket.close(); + } + catch (IOException e) { + throwUnchecked(e); + } + } + + @Override + public void randomDataAndCloseConnection() { + try { + this.socket.write(true, GARBAGE, 0, GARBAGE.length); + this.socket.flush(true); + this.socket.close(); + } + catch (IOException e) { + throwUnchecked(e); + } + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/TomcatFaultInjectorFactory.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/TomcatFaultInjectorFactory.java new file mode 100644 index 0000000000..17cbcaea2d --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/TomcatFaultInjectorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-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.wiremock; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.github.tomakehurst.wiremock.core.FaultInjector; +import com.github.tomakehurst.wiremock.servlet.FaultInjectorFactory; + +/** + * @author Dave Syer + * + */ +public class TomcatFaultInjectorFactory implements FaultInjectorFactory { + + @Override + public FaultInjector buildFaultInjector(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse) { + return new TomcatFaultInjector(httpServletResponse); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestTemplateConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestTemplateConfiguration.java new file mode 100644 index 0000000000..c543b1a556 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestTemplateConfiguration.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016-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.wiremock; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * @author Dave Syer + * + */ +@Configuration +public class WireMockRestTemplateConfiguration { + + @Bean + @ConditionalOnClass(SSLContextBuilder.class) + public RestTemplateCustomizer restTemplateCustomizer() { + return new RestTemplateCustomizer() { + @Override + public void customize(RestTemplate restTemplate) { + HttpComponentsClientHttpRequestFactory factory = (HttpComponentsClientHttpRequestFactory) restTemplate + .getRequestFactory(); + factory.setHttpClient(createSslHttpClient()); + } + + private HttpClient createSslHttpClient() { + try { + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + new SSLContextBuilder().loadTrustMaterial(null, + TrustSelfSignedStrategy.INSTANCE).build(), + NoopHostnameVerifier.INSTANCE); + return HttpClients.custom().setSSLSocketFactory(socketFactory) + .build(); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to create SSL HttpClient", + ex); + } + } + }; + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java index e23dc40879..632ccc7b3d 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java @@ -18,13 +18,14 @@ package org.springframework.cloud.contract.wiremock; import javax.net.ssl.HttpsURLConnection; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; + import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.ssl.SSLContexts; import org.junit.Assert; -import org.springframework.util.ClassUtils; -import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.springframework.util.ClassUtils; /** * Convenience factory class for a {@link WireMockConfiguration} that knows how to use diff --git a/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories b/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories index 6c450975aa..9ac5aa6677 100644 --- a/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories @@ -4,4 +4,4 @@ org.springframework.cloud.contract.wiremock.WireMockApplicationListener # RestDocs Auto Configuration org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs=\ -org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocsConfiguration \ No newline at end of file +org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocsConfiguration diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerApplicationTests.java index 34d7f57f9e..dd6a002d33 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerApplicationTests.java @@ -1,21 +1,22 @@ package org.springframework.cloud.contract.wiremock; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.assertj.core.api.Assertions.assertThat; +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:8080", webEnvironment=WebEnvironment.NONE) @@ -29,7 +30,7 @@ public class WiremockServerApplicationTests { private Service service; @Test - public void contextLoads() throws Exception { + public void hello() throws Exception { stubFor(get(urlEqualTo("/test")) .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); assertThat(this.service.go()).isEqualTo("Hello World!");