diff --git a/spring-cloud-contract-wiremock/pom.xml b/spring-cloud-contract-wiremock/pom.xml index 78de65fc2f..a1d008dfd3 100644 --- a/spring-cloud-contract-wiremock/pom.xml +++ b/spring-cloud-contract-wiremock/pom.xml @@ -24,24 +24,6 @@ - - com.github.tomakehurst - wiremock-jre8-standalone - - - io.rest-assured - rest-assured - true - - - javax.servlet - javax.servlet-api - true - - - org.apache.httpcomponents - httpclient - org.springframework.boot spring-boot-starter @@ -85,6 +67,24 @@ spring-boot-starter-test true + + com.github.tomakehurst + wiremock-jre8-standalone + + + io.rest-assured + rest-assured + true + + + javax.servlet + javax.servlet-api + true + + + org.apache.httpcomponents + httpclient + org.assertj assertj-core diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java index 661d4f9616..d2cf3f7229 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java @@ -28,6 +28,7 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -97,6 +98,9 @@ public class WireMockConfiguration implements SmartLifecycle { } registerFiles(factory); factory.notifier(new Slf4jNotifier(true)); + if (this.wireMock.getPlaceholders().isEnabled()) { + factory.extensions(new ResponseTemplateTransformer(false)); + } this.options = factory; if (this.customizer != null) { this.customizer.customize(factory); @@ -245,6 +249,8 @@ class WireMockProperties { private Server server = new Server(); + private Placeholders placeholders = new Placeholders(); + private boolean restTemplateSslEnabled; public boolean isRestTemplateSslEnabled() { @@ -263,6 +269,32 @@ class WireMockProperties { this.server = server; } + public Placeholders getPlaceholders() { + return this.placeholders; + } + + public void setPlaceholders(Placeholders placeholders) { + this.placeholders = placeholders; + } + + public class Placeholders { + + /** + * Flag to indicate that http URLs in generated wiremock stubs should be filtered + * to add or resolve a placeholder for a dynamic port. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + public static class Server { private int port = 8080; diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java index 59f2d805b6..61837aff5b 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java @@ -235,12 +235,14 @@ public final class WireMockRestServiceServer { } } - private RequestMatcher matchContents(final ContentPattern pattern) { + private RequestMatcher matchContents( + @SuppressWarnings("rawtypes") final ContentPattern pattern) { return new RequestMatcher() { @Override public void match(ClientHttpRequest request) throws IOException, AssertionError { MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + @SuppressWarnings("unchecked") MatchResult result = pattern.match(mockRequest.getBodyAsString()); MatcherAssert.assertThat( "Request as string [" + mockRequest.getBodyAsString() + "]", diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java index 88894cc3e8..03841f6f26 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocsConfiguration.java @@ -19,7 +19,12 @@ package org.springframework.cloud.contract.wiremock.restdocs; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.OperationResponseFactory; +import org.springframework.restdocs.operation.preprocess.OperationPreprocessor; /** * Custom configuration for Spring RestDocs that adds a WireMock snippet (for generating @@ -38,9 +43,42 @@ import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; public class WireMockRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer { + private final Environment environment; + + public WireMockRestDocsConfiguration(Environment environment) { + this.environment = environment; + } + @Override public void customize(MockMvcRestDocumentationConfigurer configurer) { + if (this.environment.getProperty("wiremock.placeholders.enabled", "true") + .equals("true")) { + configurer.operationPreprocessors() + .withResponseDefaults(new ForwardHeaderPreprocessor()); + } configurer.snippets().withAdditionalDefaults(new WireMockSnippet()); } + static class ForwardHeaderPreprocessor implements OperationPreprocessor { + + private final OperationResponseFactory responseFactory = new OperationResponseFactory(); + + @Override + public OperationRequest preprocess(OperationRequest request) { + return request; + } + + @Override + public OperationResponse preprocess(OperationResponse response) { + String content = response.getContentAsString(); + if (content.contains("localhost:8080")) { + content = content.replace("localhost:8080", + "localhost:{{request.requestLine.port}}"); + response = this.responseFactory.createFrom(response, content.getBytes()); + } + return response; + } + + } + } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockSnippet.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockSnippet.java index 6b76ce84c8..dc1496857d 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockSnippet.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockSnippet.java @@ -146,9 +146,14 @@ public class WireMockSnippet implements Snippet { } private ResponseDefinitionBuilder response(Operation operation) { - return aResponse().withHeaders(responseHeaders(operation)) - .withBody(operation.getResponse().getContentAsString()) - .withStatus(operation.getResponse().getStatus().value()); + String content = operation.getResponse().getContentAsString(); + ResponseDefinitionBuilder response = aResponse() + .withHeaders(responseHeaders(operation)).withBody(content); + if (content != null + && content.contains("localhost:{{request.requestLine.port}}")) { + response = response.withTransformers("response-template"); + } + return response.withStatus(operation.getResponse().getStatus().value()); } private MappingBuilder request(Operation operation) { diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockStubsApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockStubsApplicationTests.java index 7012f76a12..85bf1f6b88 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockStubsApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockStubsApplicationTests.java @@ -20,6 +20,7 @@ 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; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.junit4.SpringRunner; @@ -36,9 +37,17 @@ public class AutoConfigureWireMockStubsApplicationTests { @Autowired private Service service; + @Value("localhost:${wiremock.server.port}") + private String hostname; + @Test public void contextLoads() throws Exception { assertThat(this.service.go()).isEqualTo("Hello World"); } + @Test + public void link() throws Exception { + assertThat(this.service.link()).contains("http://" + this.hostname); + } + } diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java index eda01792ac..b69f3ea731 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java @@ -20,6 +20,8 @@ import java.io.File; import java.nio.file.Files; import java.util.Map; +import javax.servlet.http.HttpServletRequest; + import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.matching.MultiValuePattern; import com.github.tomakehurst.wiremock.stubbing.StubMapping; @@ -35,6 +37,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.contract.wiremock.WiremockServerRestDocsApplicationTests.TestConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -43,7 +46,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; +import static org.hamcrest.CoreMatchers.containsString; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -93,6 +99,24 @@ public class WiremockServerRestDocsApplicationTests { .isEqualTo(WireMock.equalTo("bar")); } + @Test + public void stubsRenderLinksWithPlaceholder() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/link")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("link:"))) + .andDo(document("link")); + + File file = new File("target/snippets/stubs", "link.json"); + BDDAssertions.then(file).exists(); + StubMapping stubMapping = StubMapping + .buildFrom(new String(Files.readAllBytes(file.toPath()))); + String body = stubMapping.getResponse().getBody(); + BDDAssertions.then(body) + .contains("http://localhost:{{request.requestLine.port}}/link"); + BDDAssertions.then(stubMapping.getResponse().getTransformers()) + .contains("response-template"); + } + @Configuration @RestController protected static class TestConfiguration { @@ -103,6 +127,14 @@ public class WiremockServerRestDocsApplicationTests { return "Hello World"; } + @ResponseBody + @RequestMapping("/link") + public String link(HttpServletRequest request) { + UriComponents uriComponents = UriComponentsBuilder + .fromHttpRequest(new ServletServerHttpRequest(request)).build(); + return "link: " + uriComponents.toUriString(); + } + @ResponseBody @RequestMapping("/status") public ResponseEntity status() { diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsHypermediaApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsHypermediaApplicationTests.java new file mode 100644 index 0000000000..f92ff03984 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsHypermediaApplicationTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2013-2019 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 + * + * https://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.File; +import java.nio.file.Files; + +import javax.servlet.http.HttpServletRequest; + +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.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.WiremockServerRestDocsHypermediaApplicationTests.TestConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestConfiguration.class, + properties = "wiremock.placeholders.enabled=false") +@AutoConfigureRestDocs(outputDir = "target/snippets") +@AutoConfigureMockMvc +public class WiremockServerRestDocsHypermediaApplicationTests { + + @Autowired + private MockMvc mockMvc; + + @Test + public void stubsRenderLinksWithoutPlaceholder() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/link")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("link:"))) + .andDo(document("link")); + + File file = new File("target/snippets/stubs", "link.json"); + BDDAssertions.then(file).exists(); + StubMapping stubMapping = StubMapping + .buildFrom(new String(Files.readAllBytes(file.toPath()))); + String body = stubMapping.getResponse().getBody(); + BDDAssertions.then(body).contains("http://localhost:8080/link"); + } + + @Configuration + @RestController + protected static class TestConfiguration { + + @ResponseBody + @RequestMapping("/link") + public String resource(HttpServletRequest request) { + UriComponents uriComponents = UriComponentsBuilder + .fromHttpRequest(new ServletServerHttpRequest(request)).build(); + return "link: " + uriComponents.toUriString(); + } + + } + +} diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockTestsApplication.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockTestsApplication.java index a15b5844af..0241b513b4 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockTestsApplication.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockTestsApplication.java @@ -87,6 +87,12 @@ class Service { return this.restTemplate.getForEntity(requestUrl, String.class).getBody(); } + public String link() { + String requestUrl = this.base + "/link"; + log.info("Will send a request to [" + requestUrl + "]"); + return this.restTemplate.getForEntity(requestUrl, String.class).getBody(); + } + public String pom() { String requestUrl = this.base + "/pom.xml"; log.info("Will send a request to [" + requestUrl + "]"); diff --git a/spring-cloud-contract-wiremock/src/test/resources/io.stubs/mappings/link.json b/spring-cloud-contract-wiremock/src/test/resources/io.stubs/mappings/link.json new file mode 100644 index 0000000000..2ee9a34e51 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/resources/io.stubs/mappings/link.json @@ -0,0 +1,12 @@ +{ + "priority": 1, + "request": { + "urlPath": "/link", + "method": "GET" + }, + "response": { + "status": 200, + "body": "{\"_links\": {\"markup\":{\"href\":\"http://localhost:{{request.requestLine.port}}/documents\"}}}", + "transformers": ["response-template"] + } +}