diff --git a/docs/src/main/asciidoc/_project-features-flows.adoc b/docs/src/main/asciidoc/_project-features-flows.adoc index e5fe8c3814..7f46a49d0b 100644 --- a/docs/src/main/asciidoc/_project-features-flows.adoc +++ b/docs/src/main/asciidoc/_project-features-flows.adoc @@ -290,7 +290,7 @@ public class ApplicationTests { .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) .andExpect(status().isOk()) .andDo(verify().jsonPath("$.id") - .stub("resource")); + .andDo(document("resource")); } } ---- @@ -317,7 +317,7 @@ following example shows: .wiremock(WireMock.post( urlPathEquals("/resource")) .withRequestBody(matchingJsonPath("$.id")) - .stub("post-resource")); + .andDo(document("post-resource")); } ---- diff --git a/samples/standalone/restdocs/http-server/src/assembly/stub.xml b/samples/standalone/restdocs/http-server/src/assembly/stub.xml index 70ae6e17f3..310a3af245 100644 --- a/samples/standalone/restdocs/http-server/src/assembly/stub.xml +++ b/samples/standalone/restdocs/http-server/src/assembly/stub.xml @@ -22,6 +22,15 @@ **/model/Fraud*.* + + ${project.build.directory}/generated-snippets/stubs + + META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings + + + **/* + + ${project.build.directory}/snippets/stubs diff --git a/samples/standalone/restdocs/http-server/src/main/resources/application.yml b/samples/standalone/restdocs/http-server/src/main/resources/application.yml index 1c421cf2b7..e69de29bb2 100644 --- a/samples/standalone/restdocs/http-server/src/main/resources/application.yml +++ b/samples/standalone/restdocs/http-server/src/main/resources/application.yml @@ -1 +0,0 @@ -server.port=0 \ No newline at end of file diff --git a/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java index d8fbcb04f1..49f81b36a6 100644 --- a/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java +++ b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java @@ -36,6 +36,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @RunWith(SpringRunner.class) @@ -69,8 +70,8 @@ public class StubGeneratorTests { .andExpect(jsonPath("$.rejectionReason").value("Amount too high")) .andDo(verify().jsonPath("$.clientId") .jsonPath("$[?(@.loanAmount > 1000)]") - .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) - .stub("markClientAsFraud")); + .contentType(MediaType.valueOf("application/vnd.fraud.v1+json"))) + .andDo(document("markClientAsFraud")); } @Test @@ -85,8 +86,8 @@ public class StubGeneratorTests { .andExpect(jsonPath("$.rejectionReason").doesNotExist()) .andDo(verify().jsonPath("$.clientId") .jsonPath("$[?(@.loanAmount <= 1000)]") - .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) - .stub("markClientAsNotFraud")); + .contentType(MediaType.valueOf("application/vnd.fraud.v1+json"))) + .andDo(document("markClientAsNotFraud")); } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStubConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStubConfiguration.java index c5c8365807..b481c56aed 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStubConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStubConfiguration.java @@ -44,17 +44,33 @@ public final class HttpServerStubConfiguration { */ public final Integer port; + /** + * Is port a random one or was it fixed. + */ + public boolean randomPort; + public HttpServerStubConfiguration(HttpServerStubConfigurer configurer, StubRunnerOptions stubRunnerOptions, StubConfiguration stubConfiguration, Integer port) { + this(configurer, stubRunnerOptions, stubConfiguration, port, randomPort(port)); + } + + public HttpServerStubConfiguration(HttpServerStubConfigurer configurer, + StubRunnerOptions stubRunnerOptions, StubConfiguration stubConfiguration, + Integer port, boolean randomPort) { this.configurer = configurer; this.stubRunnerOptions = stubRunnerOptions; this.stubConfiguration = stubConfiguration; this.port = port; + this.randomPort = randomPort; + } + + private static boolean randomPort(Integer port) { + return port == null || port == 0; } public boolean isRandomPort() { - return this.port == null || this.port == 0; + return randomPort(this.port); } public String toColonSeparatedDependencyNotation() { diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java index 69e7a92d3a..396110506f 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java @@ -274,8 +274,9 @@ class StubRunnerExecutor implements StubFinder { final List mappings = repository.getStubs(); final Collection contracts = repository.contracts; Integer port = stubRunnerOptions.port(stubConfiguration); + boolean randomPort = randomPort(port); HttpServerStubConfiguration configuration = new HttpServerStubConfiguration( - configurer, stubRunnerOptions, stubConfiguration, port); + configurer, stubRunnerOptions, stubConfiguration, port, randomPort); if (!hasRequest(contracts) && mappings.isEmpty()) { if (log.isDebugEnabled()) { log.debug("There are no HTTP related contracts. Won't start any servers"); @@ -284,7 +285,7 @@ class StubRunnerExecutor implements StubFinder { new NoOpHttpServerStub()).start(configuration); return this.stubServer; } - if (port != null && port >= 0) { + if (!randomPort) { this.stubServer = new StubServer(stubConfiguration, mappings, contracts, httpServerStub()).start(configuration); } @@ -297,7 +298,7 @@ class StubRunnerExecutor implements StubFinder { httpServerStub()).start( new HttpServerStubConfiguration(configurer, stubRunnerOptions, stubConfiguration, - availablePort)); + availablePort, true)); } }); } @@ -305,6 +306,10 @@ class StubRunnerExecutor implements StubFinder { return this.stubServer; } + private boolean randomPort(Integer port) { + return port == null || port == 0; + } + private boolean hasRequest(Collection contracts) { if (contracts.isEmpty()) { return false; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java index 046f96491f..d3fcb1a7fd 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java @@ -130,9 +130,9 @@ public class WireMockHttpServerStub implements HttpServerStub { } return this; } - int port = SocketUtils.findAvailableTcpPort(); - HttpServerStub serverStub = start(defaultConfiguration(port)); - cacheStubServer(true, port); + HttpServerStubConfiguration configuration = defaultConfiguration(); + HttpServerStub serverStub = start(configuration); + cacheStubServer(configuration.randomPort, configuration.port); return serverStub; } @@ -142,6 +142,13 @@ public class WireMockHttpServerStub implements HttpServerStub { null, port); } + private HttpServerStubConfiguration defaultConfiguration() { + int port = SocketUtils.findAvailableTcpPort(); + return new HttpServerStubConfiguration( + HttpServerStubConfigurer.NoOpHttpServerStubConfigurer.INSTANCE, null, + null, port, true); + } + @Override public HttpServerStub start(int port) { return start(defaultConfiguration(port)); @@ -175,7 +182,7 @@ public class WireMockHttpServerStub implements HttpServerStub { + " Started WireMock at [" + (this.https ? "https" : "http") + "] port [" + port + "]"); } - cacheStubServer(false, port); + cacheStubServer(configuration.randomPort, port); return this; } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubAccessor.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubAccessor.groovy new file mode 100644 index 0000000000..82b3690440 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubAccessor.groovy @@ -0,0 +1,25 @@ +/* + * 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.stubrunner.provider.wiremock + +class WireMockHttpServerStubAccessor { + + static boolean everyPortRandom() { + assert WireMockHttpServerStub.SERVERS.every { it.value.random } + return true + } +} diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy index f6457e443e..9016d3a993 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy @@ -33,6 +33,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.cloud.contract.stubrunner.HttpServerStubConfiguration import org.springframework.cloud.contract.stubrunner.StubFinder import org.springframework.cloud.contract.stubrunner.StubNotFoundException +import org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStubAccessor import org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStubConfigurer import org.springframework.context.annotation.Configuration import org.springframework.core.env.Environment @@ -75,6 +76,11 @@ class StubRunnerConfigurationSpec extends Specification { System.clearProperty("stubrunner.classifier") } + def 'should mark all ports as random'() { + expect: + WireMockHttpServerStubAccessor.everyPortRandom() + } + def 'should start WireMock servers'() { expect: 'WireMocks are running' stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy index c7f089a4b5..318eff80a3 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy @@ -748,7 +748,7 @@ class DslToWireMockClientConverterSpec extends Specification { }, "response" : { "status" : 200, - "body" : "{\\"date\\":\\"2017-01-01\\",\\"dateTime\\":\\"2017-01-01T01:23:45\\",\\"aBoolean\\":true,\\"valueWithMax\\":[1,2,3],\\"valueWithOccurrence\\":[1,2,3,4],\\"number\\":123,\\"duck\\":123,\\"alpha\\":\\"abc\\",\\"valueWithMin\\":[1,2,3],\\"time\\":\\"01:02:34\\",\\"valueWithTypeMatch\\":\\"string\\",\\"valueWithMinMax\\":[1,2,3],\\"valueWithoutAMatcher\\":\\"foo\\"}", + "body" : "{\\"duck\\":123,\\"alpha\\":\\"abc\\",\\"number\\":123,\\"aBoolean\\":true,\\"date\\":\\"2017-01-01\\",\\"dateTime\\":\\"2017-01-01T01:23:45\\",\\"time\\":\\"01:02:34\\",\\"valueWithoutAMatcher\\":\\"foo\\",\\"valueWithTypeMatch\\":\\"string\\",\\"valueWithMin\\":[1,2,3],\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3],\\"valueWithOccurrence\\":[1,2,3,4]}", "headers" : { "Content-Type" : "application/json" }, @@ -987,7 +987,7 @@ class DslToWireMockClientConverterSpec extends Specification { }, "response" : { "status" : 400, - "body" : "{\\"reason\\":{\\"@type\\":\\"ErrorReason\\",\\"description\\":\\"Bad Request\\",\\"oid\\":\\"3.7\\",\\"httpCode\\":\\"400\\"},\\"subject\\":{\\"@type\\":\\"ErrorSubject\\",\\"description\\":\\"Profile\\",\\"oid\\":\\"8.2\\"},\\"message\\":\\"[8.2 Profile/3.7 Bad Request]\\"}", + "body" : "{\\"subject\\":{\\"@type\\":\\"ErrorSubject\\",\\"oid\\":\\"8.2\\",\\"description\\":\\"Profile\\"},\\"reason\\":{\\"@type\\":\\"ErrorReason\\",\\"oid\\":\\"3.7\\",\\"description\\":\\"Bad Request\\",\\"httpCode\\":\\"400\\"},\\"message\\":\\"[8.2 Profile/3.7 Bad Request]\\"}", "headers" : { "CorrelationID" : "11111111-1111-1111-1111-111111111111", "Content-Type" : "application/json;charset=UTF-8" diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy index ed5d05bbda..1dbae89777 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy @@ -16,12 +16,11 @@ package org.springframework.cloud.contract.verifier.dsl.wiremock +import com.fasterxml.jackson.databind.ObjectMapper import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath -import groovy.json.JsonBuilder import groovy.transform.PackageScope import groovy.transform.TypeChecked -import org.json.JSONObject import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.ContractTemplate @@ -38,7 +37,6 @@ import static org.springframework.cloud.contract.verifier.util.ContentType.UNKNO import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue import static org.springframework.cloud.contract.verifier.util.ContentUtils.getClientContentType import static org.springframework.cloud.contract.verifier.util.MapConverter.transformValues - /** * Common abstraction over WireMock Request / Response conversion implementations * @@ -189,11 +187,10 @@ abstract class BaseWireMockStubStrategy { Map convertedMap = MapConverter.transformValues(value) { it instanceof GString ? it.toString() : it } as Map - String jsonOutput = new JSONObject(new JsonBuilder(convertedMap).toString()). - toString() + String jsonOutput = new ObjectMapper().writeValueAsString(convertedMap) return jsonOutput.replaceAll("\\\\\\\\\\\\", "\\\\") } - return new JsonBuilder(value).toString() + return new ObjectMapper().writeValueAsString(value) } /** diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy index b125eba2a7..3a3ad28411 100755 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy @@ -29,6 +29,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.verifier.builder.handlebars.HandlebarsEscapeHelper import org.springframework.cloud.contract.verifier.builder.handlebars.HandlebarsJsonPathHelper +import org.springframework.cloud.contract.verifier.converter.YamlContractConverter import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.util.AssertionUtil import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter @@ -78,7 +79,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"surname\\":\\"Kowalsky\\",\\"created\\":\\"2014-02-02 12:23:43\\",\\"name\\":\\"Jan\\",\\"id\\":\\"123\\"}", + "body" : "{\\"id\\":\\"123\\",\\"surname\\":\\"Kowalsky\\",\\"name\\":\\"Jan\\",\\"created\\":\\"2014-02-02 12:23:43\\"}", "headers" : { "Content-Type" : "application/json" }, @@ -131,7 +132,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"ingredients\\":[{\\"quantity\\":100,\\"type\\":\\"MALT\\"},{\\"quantity\\":200,\\"type\\":\\"WATER\\"},{\\"quantity\\":300,\\"type\\":\\"HOP\\"},{\\"quantity\\":400,\\"type\\":\\"YIEST\\"}]}", + "body" : "{\\"ingredients\\":[{\\"type\\":\\"MALT\\",\\"quantity\\":100},{\\"type\\":\\"WATER\\",\\"quantity\\":200},{\\"type\\":\\"HOP\\",\\"quantity\\":300},{\\"type\\":\\"YIEST\\",\\"quantity\\":400}]}", "transformers" : [ "response-template", "foo-transformer" ] } } @@ -191,7 +192,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response": { "status": 204, - "body": "{\\"foundExistingPayment\\":false,\\"paymentId\\":\\"4\\"}", + "body": "{\\"paymentId\\":\\"4\\",\\"foundExistingPayment\\":false}", "transformers" : [ "response-template", "foo-transformer" ] } } @@ -1968,7 +1969,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"number\\":0,\\"last\\":true,\\"numberOfElements\\":1,\\"size\\":1,\\"totalPages\\":1,\\"sort\\":[{\\"nullHandling\\":\\"NATIVE\\",\\"ignoreCase\\":false,\\"property\\":\\"id\\",\\"ascending\\":true,\\"direction\\":\\"ASC\\"}],\\"content\\":[{\\"id\\":\\"00000000-0000-0000-0000-000000000000\\",\\"state\\":\\"ACTIVE\\",\\"type\\":\\"Extraordinary\\"}],\\"first\\":true,\\"totalElements\\":1}", + "body" : "{\\"content\\":[{\\"id\\":\\"00000000-0000-0000-0000-000000000000\\",\\"type\\":\\"Extraordinary\\",\\"state\\":\\"ACTIVE\\"}],\\"totalPages\\":1,\\"totalElements\\":1,\\"last\\":true,\\"sort\\":[{\\"direction\\":\\"ASC\\",\\"property\\":\\"id\\",\\"ignoreCase\\":false,\\"nullHandling\\":\\"NATIVE\\",\\"ascending\\":true}],\\"first\\":true,\\"numberOfElements\\":1,\\"size\\":1,\\"number\\":0}", "transformers" : [ "response-template", "foo-transformer" ] } } @@ -2216,7 +2217,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"rawAuthorization2\\":\\"{{request.headers.Authorization.[1]}}\\",\\"responseBaz\\":{{{jsonPath request.body '$.baz'}}} ,\\"pathIndex\\":\\"{{{request.path.[1]}}}\\",\\"rawAuthorization\\":\\"{{request.headers.Authorization.[0]}}\\",\\"authorization2\\":\\"{{{request.headers.Authorization.[1]}}}\\",\\"rawParam\\":\\"{{request.query.foo.[0]}}\\",\\"url\\":\\"{{{request.url}}}\\",\\"paramIndex\\":\\"{{{request.query.foo.[1]}}}\\",\\"authorization\\":\\"{{{request.headers.Authorization.[0]}}}\\",\\"path\\":\\"{{{request.path}}}\\",\\"rawUrl\\":\\"{{request.url}}\\",\\"rawPath\\":\\"{{request.path}}\\",\\"rawResponseBaz2\\":\\"Bla bla {{jsonPath request.body '$.foo'}} bla bla\\",\\"param\\":\\"{{{request.query.foo.[0]}}}\\",\\"rawResponseBaz\\":{{jsonPath request.body '$.baz'}} ,\\"responseBaz2\\":\\"Bla bla {{{jsonPath request.body '$.foo'}}} bla bla\\",\\"rawResponseFoo\\":\\"{{jsonPath request.body '$.foo'}}\\",\\"responseFoo\\":\\"{{{jsonPath request.body '$.foo'}}}\\",\\"rawPathIndex\\":\\"{{request.path.[1]}}\\",\\"fullBody\\":\\"{{{escapejsonbody}}}\\",\\"rawParamIndex\\":\\"{{request.query.foo.[1]}}\\"}", + "body" : "{\\"url\\":\\"{{{request.url}}}\\",\\"path\\":\\"{{{request.path}}}\\",\\"pathIndex\\":\\"{{{request.path.[1]}}}\\",\\"param\\":\\"{{{request.query.foo.[0]}}}\\",\\"paramIndex\\":\\"{{{request.query.foo.[1]}}}\\",\\"authorization\\":\\"{{{request.headers.Authorization.[0]}}}\\",\\"authorization2\\":\\"{{{request.headers.Authorization.[1]}}}\\",\\"fullBody\\":\\"{{{escapejsonbody}}}\\",\\"responseFoo\\":\\"{{{jsonPath request.body '$.foo'}}}\\",\\"responseBaz\\":{{{jsonPath request.body '$.baz'}}} ,\\"responseBaz2\\":\\"Bla bla {{{jsonPath request.body '$.foo'}}} bla bla\\",\\"rawUrl\\":\\"{{request.url}}\\",\\"rawPath\\":\\"{{request.path}}\\",\\"rawPathIndex\\":\\"{{request.path.[1]}}\\",\\"rawParam\\":\\"{{request.query.foo.[0]}}\\",\\"rawParamIndex\\":\\"{{request.query.foo.[1]}}\\",\\"rawAuthorization\\":\\"{{request.headers.Authorization.[0]}}\\",\\"rawAuthorization2\\":\\"{{request.headers.Authorization.[1]}}\\",\\"rawResponseFoo\\":\\"{{jsonPath request.body '$.foo'}}\\",\\"rawResponseBaz\\":{{jsonPath request.body '$.baz'}} ,\\"rawResponseBaz2\\":\\"Bla bla {{jsonPath request.body '$.foo'}} bla bla\\"}", "headers" : { "Authorization" : "{{{request.headers.Authorization.[0]}}};foo" }, @@ -2327,7 +2328,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"access_token\\":\\"RANDOM_ACCESS_TOKEN\\",\\"refresh_token\\":\\"RANDOM_REFRESH_TOKEN\\",\\"scope\\":[\\"task\\"],\\"token_type\\":\\"bearer\\",\\"expires_in\\":3600,\\"user\\":{\\"name\\":\\"User\\",\\"id\\":1,\\"username\\":\\"user\\"}}", + "body" : "{\\"refresh_token\\":\\"RANDOM_REFRESH_TOKEN\\",\\"access_token\\":\\"RANDOM_ACCESS_TOKEN\\",\\"token_type\\":\\"bearer\\",\\"expires_in\\":3600,\\"scope\\":[\\"task\\"],\\"user\\":{\\"id\\":1,\\"username\\":\\"user\\",\\"name\\":\\"User\\"}}", "headers" : { "Content-Type" : "application/json" }, @@ -2570,7 +2571,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"code\\":91015,\\"payload\\":null,\\"description\\":\\"订单已失效\\",\\"lastUpdateTime\\":\\"0\\"}", + "body" : "{\\"code\\":91015,\\"description\\":\\"订单已失效\\",\\"lastUpdateTime\\":\\"0\\",\\"payload\\":null}", "transformers" : [ "response-template", "foo-transformer" ] } } @@ -2641,7 +2642,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie }, "response" : { "status" : 200, - "body" : "{\\"access_token\\":\\"RANDOM_ACCESS_TOKEN\\",\\"refresh_token\\":\\"RANDOM_REFRESH_TOKEN\\",\\"scope\\":[\\"task\\"],\\"token_type\\":\\"bearer\\",\\"expires_in\\":3600,\\"user\\":{\\"name\\":\\"User\\",\\"id\\":1,\\"username\\":\\"user\\"}}", + "body" : "{\\"refresh_token\\":\\"RANDOM_REFRESH_TOKEN\\",\\"access_token\\":\\"RANDOM_ACCESS_TOKEN\\",\\"token_type\\":\\"bearer\\",\\"expires_in\\":3600,\\"scope\\":[\\"task\\"],\\"user\\":{\\"id\\":1,\\"username\\":\\"user\\",\\"name\\":\\"User\\"}}", "headers" : { "Content-Type" : "application/json;charset=UTF-8" }, @@ -2844,6 +2845,23 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie } + @Issue("#1038") + def "should deal with unicode strings"() { + given: + File file = new File(WireMockGroovyDslSpec.getResource("/yml/issue1038.yml"). + toURI()) + Contract contractDsl = new YamlContractConverter().convertFrom(file).first() + when: + String wireMockStub = new WireMockStubStrategy("Test", + new ContractMetadata(null, false, 0, null, contractDsl), contractDsl) + .toWireMockClientStub() + + then: + wireMockStub.contains('''\\"country\\":\\"日本\\"''') + stubMappingIsValidWireMockStub(wireMockStub) + + } + WireMockConfiguration config() { return new WireMockConfiguration().extensions(responseTemplateTransformer()) } diff --git a/spring-cloud-contract-verifier/src/test/resources/yml/issue1038.yml b/spring-cloud-contract-verifier/src/test/resources/yml/issue1038.yml new file mode 100644 index 0000000000..ea749fd479 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/yml/issue1038.yml @@ -0,0 +1,15 @@ +name: get-all-users +request: + url: /users + method: GET +response: + status: 200 + headers: + content-type: application/json; charset=UTF-8 + body: + - id: 001 + name: alice + country: US + - id: 002 + name: bob + country: 日本 \ No newline at end of file diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/SpringCloudContractRestDocs.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/SpringCloudContractRestDocs.java index ef9f1191d6..531080fa8c 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/SpringCloudContractRestDocs.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/SpringCloudContractRestDocs.java @@ -47,8 +47,7 @@ import org.springframework.restdocs.snippet.Snippet; * // first WireMock * .andDo(WireMockRestDocs.verify() * .jsonPath("$[?(@.foo >= 20)]") - * .contentType(MediaType.valueOf("application/json")) - * .stub("shouldGrantABeerIfOldEnough")) + * .contentType(MediaType.valueOf("application/json"))) * // then Contract DSL documentation * .andDo(document("index", SpringCloudContractRestDocs.dslContract())); * } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java index 5982a84627..c4c9db6b36 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java @@ -33,7 +33,8 @@ package org.springframework.cloud.contract.wiremock.restdocs; * public void contextLoads() throws Exception { * mockMvc.perform(get("/resource")) * .andExpect(content().string("Hello World")) - * .andDo(verify().stub("resource")); + * .andDo(verify()) + * .andDo(document("resource")); * } * which creates a file "target/snippets/stubs/resource.json" matching any GET * request to "/resource". To match POST and PUT, you can also specify the content type diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java index 6bfe920fb8..43a73b7130 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java @@ -42,20 +42,19 @@ public abstract class WireMockVerifyHelperandDo(document(name)) + */ @SuppressWarnings("unchecked") + @Deprecated public S stub(String name) { - this.name = name; return (S) this; } - protected String getName() { - return this.name; - } - public void configure(T result) { Map configuration = getConfiguration(result); String actual = new String(getRequestBodyContent(result), diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java index 36998b827d..7b02bb6c92 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java @@ -16,6 +16,8 @@ package org.springframework.cloud.contract.wiremock; +import java.io.File; + import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.Rule; import org.junit.Test; @@ -34,12 +36,16 @@ 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.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.util.FileSystemUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; + @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) @AutoConfigureRestDocs(outputDir = "target/snippets") @@ -54,14 +60,16 @@ public class WiremockServerRestDocsMatcherApplicationTests { @Test public void matchesRequest() throws Exception { + FileSystemUtils.deleteRecursively(new File("target/snippets/stubs/posted.json")); this.mockMvc .perform(MockMvcRequestBuilders.post("/resource").content("greeting") .contentType(MediaType.TEXT_PLAIN)) .andExpect(MockMvcResultMatchers.content().string("Hello World")) .andDo(WireMockRestDocs.verify() .wiremock(WireMock.post(WireMock.urlPathEqualTo("/resource")) - .withRequestBody(WireMock.matching("greeting.*"))) - .stub("posted")); + .withRequestBody(WireMock.matching("greeting.*")))) + .andDo(document("posted")); + assertThat(new File("target/snippets/stubs/posted.json")).exists(); } @Test @@ -74,8 +82,8 @@ public class WiremockServerRestDocsMatcherApplicationTests { .andExpect(MockMvcResultMatchers.content().string("Hello World")) .andDo(WireMockRestDocs.verify() .wiremock(WireMock.post(WireMock.urlPathEqualTo("/resource")) - .withRequestBody(WireMock.matching("garbage.*"))) - .stub("posted")); + .withRequestBody(WireMock.matching("garbage.*")))) + .andDo(document("posted")); } @Configuration diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/restdocs/ContractDslSnippetTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/restdocs/ContractDslSnippetTests.java index a163041627..d5bd2ee40a 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/restdocs/ContractDslSnippetTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/restdocs/ContractDslSnippetTests.java @@ -77,7 +77,9 @@ public class ContractDslSnippetTests { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation)).build(); + .apply(documentationConfiguration(this.restDocumentation).snippets() + .withAdditionalDefaults(new WireMockSnippet())) + .build(); } @Test @@ -92,13 +94,13 @@ public class ContractDslSnippetTests { // first WireMock .andDo(WireMockRestDocs.verify().jsonPath("$[?(@.foo >= 20)]") .jsonPath("$[?(@.bar in ['baz','bazz','bazzz'])]") - .contentType(MediaType.valueOf("application/json")) - .stub("shouldGrantABeerIfOldEnough")) + .contentType(MediaType.valueOf("application/json"))) // then Contract DSL documentation .andDo(document("index", SpringCloudContractRestDocs.dslContract())); // end::contract_snippet[] then(file("/contracts/index.groovy")).exists(); + then(file("/stubs/index.json")).exists(); then(file("/index/dsl-contract.adoc")).exists(); Collection parsedContracts = ContractVerifierDslConverter .convertAsCollection(new File("/"), file("/contracts/index.groovy")); @@ -131,8 +133,7 @@ public class ContractDslSnippetTests { // first WireMock .andDo(WireMockRestDocs.verify().jsonPath("$[?(@.foo >= 20)]") .jsonPath("$[?(@.bar in ['baz','bazz','bazzz'])]") - .contentType(MediaType.valueOf("application/json")) - .stub("shouldGrantABeerIfOldEnough")) + .contentType(MediaType.valueOf("application/json"))) // then Contract DSL documentation .andDo(document("{methodName}", SpringCloudContractRestDocs.dslContract())); @@ -140,6 +141,9 @@ public class ContractDslSnippetTests { then(file( "/contracts/should_create_contract_template_and_doc_with_placeholder_names.groovy")) .exists(); + then(file( + "/stubs/should_create_contract_template_and_doc_with_placeholder_names.json")) + .exists(); then(file( "/should_create_contract_template_and_doc_with_placeholder_names/dsl-contract.adoc")) .exists();