diff --git a/docs/build.gradle b/docs/build.gradle index 68c54bcb..92d3a897 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -18,4 +18,5 @@ asciidoctor { } attributes 'revnumber': project.version, 'branch-or-tag': project.version.endsWith('SNAPSHOT') ? 'master': "v${project.version}" + inputs.files(sourceSets.test.java) } \ No newline at end of file diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 3915fecb..8a526df7 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -196,6 +196,32 @@ is not found in the request. +[[documenting-your-api-path-parameters]] +=== Path parameters + +A request's path parameters can be documented using `withPathParameters` + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters] +---- +<1> Build the request. Uses the static `get` method on `RestDocumentationRequestBuilders`. +<2> Use `withPathParameters` to describe the path parameters. +<3> Document a parameter named `longitude`. Uses the static `parameterWithName` method on +`org.springframework.restdocs.request.RequestDocumentation`. +<4> Document a parameter named `latitude`. + +The result is a snippet named `path-parameters.adoc` that contains a table describing +the path parameters that are supported by the resource. + +When documenting path parameters, the test will fail if an undocumented path parameter +is used in the request. Similarly, the test will also fail if a documented path parameter +is not found in the request. + +TIP: To make the path parameters available for documentation, the request must be +built using one of the methods on `RestDocumentationRequestBuilders` rather than +`MockMvcRequestBuilders`. + [[documenting-your-api-default-snippets]] === Default snippets @@ -286,8 +312,8 @@ override the template for the `curl-request.adoc` snippet, create a template nam There are two ways to provide extra information for inclusion in a generated snippet: -. Use the `attributes` method on a field, link or query parameter descriptor to add one or - more attributes to an individual descriptor +. Use the `attributes` method on a field, link or parameter descriptor to add one or more + attributes to an individual descriptor. . Pass in some attributes when calling `withCurlRequest`, `withHttpRequest`, `withHttpResponse`, etc on `RestDocumentationResultHandler`. Such attributes will be associated with the snippet as a whole. diff --git a/docs/src/test/java/com/example/Hypermedia.java b/docs/src/test/java/com/example/Hypermedia.java index a9e0de17..09b8e2d2 100644 --- a/docs/src/test/java/com/example/Hypermedia.java +++ b/docs/src/test/java/com/example/Hypermedia.java @@ -17,7 +17,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.LinkExtractors.halLinks; diff --git a/docs/src/test/java/com/example/InvokeService.java b/docs/src/test/java/com/example/InvokeService.java index 18fc8508..32fdaf65 100644 --- a/docs/src/test/java/com/example/InvokeService.java +++ b/docs/src/test/java/com/example/InvokeService.java @@ -17,7 +17,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.http.MediaType; diff --git a/docs/src/test/java/com/example/PathParameters.java b/docs/src/test/java/com/example/PathParameters.java new file mode 100644 index 00000000..4260b6d7 --- /dev/null +++ b/docs/src/test/java/com/example/PathParameters.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014-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 com.example; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.springframework.test.web.servlet.MockMvc; + +public class PathParameters { + + private MockMvc mockMvc; + + public void pathParameters() throws Exception { + // tag::path-parameters[] + this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) // <1> + .andExpect(status().isOk()) + .andDo(document("locations").withPathParameters( // <2> + parameterWithName("latitude").description("The location's latitude"), // <3> + parameterWithName("longitude").description("The location's longitude") // <4> + )); + // end::path-parameters[] + } + +} diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/Payload.java index 7dc57552..121dd114 100644 --- a/docs/src/test/java/com/example/Payload.java +++ b/docs/src/test/java/com/example/Payload.java @@ -20,8 +20,8 @@ import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.http.MediaType; diff --git a/docs/src/test/java/com/example/QueryParameters.java b/docs/src/test/java/com/example/QueryParameters.java index ca81142c..f4af6037 100644 --- a/docs/src/test/java/com/example/QueryParameters.java +++ b/docs/src/test/java/com/example/QueryParameters.java @@ -18,7 +18,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.test.web.servlet.MockMvc; diff --git a/docs/src/test/java/com/example/ResponsePostProcessing.java b/docs/src/test/java/com/example/ResponsePostProcessing.java index c9a6bcb0..a0c8d8ae 100644 --- a/docs/src/test/java/com/example/ResponsePostProcessing.java +++ b/docs/src/test/java/com/example/ResponsePostProcessing.java @@ -17,7 +17,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.modifyResponseTo; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.test.web.servlet.MockMvc; diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java index 80a762ad..5fa7364a 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java @@ -22,9 +22,9 @@ import static org.springframework.restdocs.RestDocumentation.documentationConfig import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -279,7 +279,7 @@ public class ApiDocumentation { fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(), fieldWithPath("body").description("The body of the note").type(FieldType.STRING).optional(), fieldWithPath("tags").description("An array of tag resource URIs").optional())); - + } @Test diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java index aca7a580..a6afc59f 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java @@ -21,9 +21,9 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -155,7 +155,7 @@ public class GettingStartedDocumentation { return noteLocation; } - void getTags(String noteTagsLocation) throws Exception { + void getTags(String noteTagsLocation) throws Exception { this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.tags", hasSize(1))); @@ -177,7 +177,7 @@ public class GettingStartedDocumentation { .andReturn(); } - void getTagsForExistingNote(String noteTagsLocation) throws Exception { + void getTagsForExistingNote(String noteTagsLocation) throws Exception { this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.tags", hasSize(1))); diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java index 4f397569..1901ea85 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java @@ -22,9 +22,9 @@ import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java index 2ca0d48b..79b78051 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java @@ -21,9 +21,9 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -155,7 +155,7 @@ public class GettingStartedDocumentation { return noteLocation; } - void getTags(String noteTagsLocation) throws Exception { + void getTags(String noteTagsLocation) throws Exception { this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.tags", hasSize(1))); @@ -177,7 +177,7 @@ public class GettingStartedDocumentation { .andReturn(); } - void getTagsForExistingNote(String noteTagsLocation) throws Exception { + void getTagsForExistingNote(String noteTagsLocation) throws Exception { this.mockMvc.perform(get(noteTagsLocation)) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.tags", hasSize(1))); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java new file mode 100644 index 00000000..e702fdaf --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java @@ -0,0 +1,265 @@ +/* + * Copyright 2014-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.restdocs; + +import java.net.URI; + +import org.springframework.http.HttpMethod; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +/** + * A drop-in replacement for {@link MockMvcRequestBuilders} that captures a request's URL + * template and makes it available for documentation. Required when + * {@link RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs .request.ParameterDescriptor...) + * documenting path parameters} and recommended for general usage. + * + * @author Andy Wilkinson + * @see MockMvcRequestBuilders + * @see RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs.request.ParameterDescriptor...) + * @see RestDocumentationResultHandler#withPathParameters(java.util.Map, + * org.springframework.restdocs.request.ParameterDescriptor...) + */ +public abstract class RestDocumentationRequestBuilders { + + private static final String ATTRIBUTE_NAME_URL_TEMPLATE = "org.springframework.restdocs.urlTemplate"; + + private RestDocumentationRequestBuilders() { + + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a GET request. The url template + * will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the GET request + */ + public static MockHttpServletRequestBuilder get(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.get(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a GET request. + * + * @param uri the URL + * @return the builder for the GET request + */ + public static MockHttpServletRequestBuilder get(URI uri) { + return MockMvcRequestBuilders.get(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a POST request. The url template + * will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the POST request + */ + public static MockHttpServletRequestBuilder post(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.post(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a POST request. + * + * @param uri the URL + * @return the builder for the POST request + */ + public static MockHttpServletRequestBuilder post(URI uri) { + return MockMvcRequestBuilders.post(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PUT request. The url template + * will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the PUT request + */ + public static MockHttpServletRequestBuilder put(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.put(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PUT request. + * + * @param uri the URL + * @return the builder for the PUT request + */ + public static MockHttpServletRequestBuilder put(URI uri) { + return MockMvcRequestBuilders.put(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PATCH request. The url + * template will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the PATCH request + */ + public static MockHttpServletRequestBuilder patch(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.patch(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PATCH request. + * + * @param uri the URL + * @return the builder for the PATCH request + */ + public static MockHttpServletRequestBuilder patch(URI uri) { + return MockMvcRequestBuilders.patch(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a DELETE request. The url + * template will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the DELETE request + */ + public static MockHttpServletRequestBuilder delete(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.delete(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a DELETE request. + * + * @param uri the URL + * @return the builder for the DELETE request + */ + public static MockHttpServletRequestBuilder delete(URI uri) { + return MockMvcRequestBuilders.delete(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for an OPTIONS request. The url + * template will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the OPTIONS request + */ + public static MockHttpServletRequestBuilder options(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.options(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for an OPTIONS request. + * + * @param uri the URL + * @return the builder for the OPTIONS request + */ + public static MockHttpServletRequestBuilder options(URI uri) { + return MockMvcRequestBuilders.options(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a HEAD request. The url template + * will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the HEAD request + */ + public static MockHttpServletRequestBuilder head(String urlTemplate, + Object... urlVariables) { + return MockMvcRequestBuilders.head(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a HEAD request. + * + * @param uri the URL + * @return the builder for the HEAD request + */ + public static MockHttpServletRequestBuilder head(URI uri) { + return MockMvcRequestBuilders.head(uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP + * method. The url template will be captured and made available for documentation. + * + * @param httpMethod the HTTP method + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the request + */ + public static MockHttpServletRequestBuilder request(HttpMethod httpMethod, + String urlTemplate, Object... urlVariables) { + return MockMvcRequestBuilders.request(httpMethod, urlTemplate, urlVariables) + .requestAttr(ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP + * method. + * @param httpMethod the HTTP method (GET, POST, etc) + * @param uri the URL + * @return the builder for the request + */ + public static MockHttpServletRequestBuilder request(HttpMethod httpMethod, URI uri) { + return MockMvcRequestBuilders.request(httpMethod, uri); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a multipart request. The url + * template will be captured and made available for documentation. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + * @return the builder for the file upload request + */ + public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, + Object... urlVariables) { + return (MockMultipartHttpServletRequestBuilder) MockMvcRequestBuilders + .fileUpload(urlTemplate, urlVariables).requestAttr( + ATTRIBUTE_NAME_URL_TEMPLATE, urlTemplate); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a multipart request. + * + * @param uri the URL + * @return the builder for the file upload request + */ + public static MockMultipartHttpServletRequestBuilder fileUpload(URI uri) { + return MockMvcRequestBuilders.fileUpload(uri); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java index 1d8a0445..a8134f87 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java @@ -22,6 +22,7 @@ import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRe import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks; import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; +import static org.springframework.restdocs.request.RequestDocumentation.documentPathParameters; import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; import java.util.ArrayList; @@ -309,6 +310,45 @@ public class RestDocumentationResultHandler implements ResultHandler { return this; } + /** + * Documents the parameters in the request's path using the given {@code descriptors}. + *

+ * If a parameter is present in the path but is not described by one of the + * descriptors a failure will occur when this handler is invoked. Similarly, if a + * parameter is described but is not present in the request a failure will also occur + * when this handler is invoked. + * + * @param descriptors the parameter descriptors + * @return {@code this} + * @see RequestDocumentation#parameterWithName(String) + */ + public RestDocumentationResultHandler withPathParameters( + ParameterDescriptor... descriptors) { + return this.withQueryParameters(null, descriptors); + } + + /** + * Documents the parameters in the request's path using the given {@code descriptors}. + * The given {@code attributes} are made available during the generation of the path + * parameters snippet. + *

+ * If a parameter is present in the path but is not described by one of the + * descriptors a failure will occur when this handler is invoked. Similarly, if a + * parameter is described but is not present in the request a failure will also occur + * when this handler is invoked. + * + * @param descriptors the parameter descriptors + * @param attributes the attributes + * @return {@code this} + * @see RequestDocumentation#parameterWithName(String) + */ + public RestDocumentationResultHandler withPathParameters( + Map attributes, ParameterDescriptor... descriptors) { + this.delegates.add(documentPathParameters(this.identifier, attributes, + descriptors)); + return this; + } + @Override public void handle(MvcResult result) throws Exception { this.curlRequest.handle(result); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippetResultHandler.java new file mode 100644 index 00000000..cc4e630b --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippetResultHandler.java @@ -0,0 +1,67 @@ +package org.springframework.restdocs.request; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.Assert; + +public abstract class AbstractParametersSnippetResultHandler extends + SnippetWritingResultHandler { + + private final Map descriptorsByName = new LinkedHashMap<>(); + + protected AbstractParametersSnippetResultHandler(String identifier, + String snippetName, Map attributes, + ParameterDescriptor... descriptors) { + super(identifier, snippetName, attributes); + for (ParameterDescriptor descriptor : descriptors) { + Assert.hasText(descriptor.getName()); + Assert.hasText(descriptor.getDescription()); + this.descriptorsByName.put(descriptor.getName(), descriptor); + } + } + + @Override + protected Map doHandle(MvcResult result) throws IOException { + verifyParameterDescriptors(result); + + Map model = new HashMap<>(); + List> parameters = new ArrayList<>(); + for (Entry entry : this.descriptorsByName.entrySet()) { + parameters.add(entry.getValue().toModel()); + } + model.put("parameters", parameters); + return model; + } + + protected void verifyParameterDescriptors(MvcResult result) { + Set actualParameters = extractActualParameters(result); + Set expectedParameters = this.descriptorsByName.keySet(); + Set undocumentedParameters = new HashSet(actualParameters); + undocumentedParameters.removeAll(expectedParameters); + Set missingParameters = new HashSet(expectedParameters); + missingParameters.removeAll(actualParameters); + + if (!undocumentedParameters.isEmpty() || !missingParameters.isEmpty()) { + verificationFailed(undocumentedParameters, missingParameters); + } + else { + Assert.isTrue(actualParameters.equals(expectedParameters)); + } + } + + protected abstract Set extractActualParameters(MvcResult result); + + protected abstract void verificationFailed(Set undocumentedParameters, + Set missingParameters); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippetResultHandler.java new file mode 100644 index 00000000..70298a35 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippetResultHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright 2014-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.restdocs.request; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.restdocs.snippet.SnippetGenerationException; +import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.Assert; + +/** + * A {@link SnippetWritingResultHandler} that produces a snippet documenting the path + * parameters supported by a RESTful resource. + * + * @author Andy Wilkinson + */ +public class PathParametersSnippetResultHandler extends + AbstractParametersSnippetResultHandler { + + private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); + + protected PathParametersSnippetResultHandler(String identifier, + Map attributes, ParameterDescriptor... descriptors) { + super(identifier, "path-parameters", attributes, descriptors); + } + + @Override + protected Set extractActualParameters(MvcResult result) { + String urlTemplate = extractUrlTemplate(result); + Matcher matcher = NAMES_PATTERN.matcher(urlTemplate); + Set actualParameters = new HashSet<>(); + while (matcher.find()) { + String match = matcher.group(1); + actualParameters.add(getParameterName(match)); + } + return actualParameters; + } + + private String extractUrlTemplate(MvcResult result) { + String urlTemplate = (String) result.getRequest().getAttribute( + "org.springframework.restdocs.urlTemplate"); + Assert.notNull(urlTemplate, + "urlTemplate not found. Did you use RestDocumentationRequestBuilders to " + + "build the request?"); + return urlTemplate; + } + + private static String getParameterName(String match) { + int colonIdx = match.indexOf(':'); + return (colonIdx != -1 ? match.substring(0, colonIdx) : match); + } + + @Override + protected void verificationFailed(Set undocumentedParameters, + Set missingParameters) { + String message = ""; + if (!undocumentedParameters.isEmpty()) { + message += "Path parameters with the following names were not documented: " + + undocumentedParameters; + } + if (!missingParameters.isEmpty()) { + if (message.length() > 0) { + message += ". "; + } + message += "Path parameters with the following names were not found in " + + "the request: " + missingParameters; + } + throw new SnippetGenerationException(message); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java index b61e85c8..45a3f924 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java @@ -16,20 +16,12 @@ package org.springframework.restdocs.request; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import org.springframework.restdocs.snippet.SnippetGenerationException; import org.springframework.restdocs.snippet.SnippetWritingResultHandler; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.Assert; /** * A {@link SnippetWritingResultHandler} that produces a snippet documenting the query @@ -37,60 +29,35 @@ import org.springframework.util.Assert; * * @author Andy Wilkinson */ -public class QueryParametersSnippetResultHandler extends SnippetWritingResultHandler { - - private final Map descriptorsByName = new LinkedHashMap<>(); +public class QueryParametersSnippetResultHandler extends + AbstractParametersSnippetResultHandler { protected QueryParametersSnippetResultHandler(String identifier, Map attributes, ParameterDescriptor... descriptors) { - super(identifier, "query-parameters", attributes); - for (ParameterDescriptor descriptor : descriptors) { - Assert.hasText(descriptor.getName()); - Assert.hasText(descriptor.getDescription()); - this.descriptorsByName.put(descriptor.getName(), descriptor); - } + super(identifier, "query-parameters", attributes, descriptors); } @Override - protected Map doHandle(MvcResult result) throws IOException { - verifyParameterDescriptors(result); - - Map model = new HashMap<>(); - List> parameters = new ArrayList<>(); - for (Entry entry : this.descriptorsByName.entrySet()) { - parameters.add(entry.getValue().toModel()); + protected void verificationFailed(Set undocumentedParameters, + Set missingParameters) { + String message = ""; + if (!undocumentedParameters.isEmpty()) { + message += "Query parameters with the following names were not documented: " + + undocumentedParameters; } - model.put("parameters", parameters); - return model; + if (!missingParameters.isEmpty()) { + if (message.length() > 0) { + message += ". "; + } + message += "Query parameters with the following names were not found in the request: " + + missingParameters; + } + throw new SnippetGenerationException(message); } - private void verifyParameterDescriptors(MvcResult result) { - Set actualParameters = result.getRequest().getParameterMap().keySet(); - Set expectedParameters = this.descriptorsByName.keySet(); - - Set undocumentedParameters = new HashSet(actualParameters); - undocumentedParameters.removeAll(expectedParameters); - - Set missingParameters = new HashSet(expectedParameters); - missingParameters.removeAll(actualParameters); - - if (!undocumentedParameters.isEmpty() || !missingParameters.isEmpty()) { - String message = ""; - if (!undocumentedParameters.isEmpty()) { - message += "Query parameters with the following names were not documented: " - + undocumentedParameters; - } - if (!missingParameters.isEmpty()) { - if (message.length() > 0) { - message += ". "; - } - message += "Query parameters with the following names were not found in the request: " - + missingParameters; - } - throw new SnippetGenerationException(message); - } - - Assert.isTrue(actualParameters.equals(expectedParameters)); + @Override + protected Set extractActualParameters(MvcResult result) { + return result.getRequest().getParameterMap().keySet(); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java index aadcf76a..3b45c3ec 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java @@ -32,6 +32,22 @@ public abstract class RequestDocumentation { } + /** + * Creates a {@link SnippetWritingResultHandler} that will produce a snippet + * documenting a request's path parameters. + * + * @param identifier An identifier for the API call that is being documented + * @param attributes Attributes made available during rendering of the path parameters + * snippet + * @param descriptors The descriptions of the parameters in the request's path + * @return the result handler + * @see RestDocumentationResultHandler#withPathParameters(ParameterDescriptor...) + */ + public static SnippetWritingResultHandler documentPathParameters(String identifier, + Map attributes, ParameterDescriptor... descriptors) { + return new PathParametersSnippetResultHandler(identifier, attributes, descriptors); + } + /** * Creates a {@link SnippetWritingResultHandler} that will produce a snippet * documenting a request's query parameters diff --git a/spring-restdocs/src/main/resources/org/springframework/restdocs/templates/default-path-parameters.snippet b/spring-restdocs/src/main/resources/org/springframework/restdocs/templates/default-path-parameters.snippet new file mode 100644 index 00000000..067e1fb8 --- /dev/null +++ b/spring-restdocs/src/main/resources/org/springframework/restdocs/templates/default-path-parameters.snippet @@ -0,0 +1,9 @@ +|=== +|Parameter|Description + +{{#parameters}} +|{{name}} +|{{description}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java index 660dc688..b2ba9bb6 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentation.modifyResponseTo; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks; import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent; import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders; @@ -30,7 +31,6 @@ import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.httpResponse; import static org.springframework.restdocs.test.SnippetMatchers.snippet; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.File; diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationRequestBuildersTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationRequestBuildersTests.java new file mode 100644 index 00000000..aebc1feb --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationRequestBuildersTests.java @@ -0,0 +1,157 @@ +/* + * Copyright 2014-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.restdocs; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.fileUpload; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.head; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.options; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.request; + +import java.net.URI; + +import javax.servlet.ServletContext; + +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +/** + * Tests for {@link RestDocumentationRequestBuilders} + * + * @author Andy Wilkinson + * + */ +public class RestDocumentationRequestBuildersTests { + + private final ServletContext servletContext = new MockServletContext(); + + @Test + public void getTemplate() { + assertTemplate(get("{template}", "t"), HttpMethod.GET); + } + + @Test + public void getUri() { + assertUri(get(URI.create("/uri")), HttpMethod.GET); + } + + @Test + public void postTemplate() { + assertTemplate(post("{template}", "t"), HttpMethod.POST); + } + + @Test + public void postUri() { + assertUri(post(URI.create("/uri")), HttpMethod.POST); + } + + @Test + public void putTemplate() { + assertTemplate(put("{template}", "t"), HttpMethod.PUT); + } + + @Test + public void putUri() { + assertUri(put(URI.create("/uri")), HttpMethod.PUT); + } + + @Test + public void patchTemplate() { + assertTemplate(patch("{template}", "t"), HttpMethod.PATCH); + } + + @Test + public void patchUri() { + assertUri(patch(URI.create("/uri")), HttpMethod.PATCH); + } + + @Test + public void deleteTemplate() { + assertTemplate(delete("{template}", "t"), HttpMethod.DELETE); + } + + @Test + public void deleteUri() { + assertUri(delete(URI.create("/uri")), HttpMethod.DELETE); + } + + @Test + public void optionsTemplate() { + assertTemplate(options("{template}", "t"), HttpMethod.OPTIONS); + } + + @Test + public void optionsUri() { + assertUri(options(URI.create("/uri")), HttpMethod.OPTIONS); + } + + @Test + public void headTemplate() { + assertTemplate(head("{template}", "t"), HttpMethod.HEAD); + } + + @Test + public void headUri() { + assertUri(head(URI.create("/uri")), HttpMethod.HEAD); + } + + @Test + public void requestTemplate() { + assertTemplate(request(HttpMethod.GET, "{template}", "t"), HttpMethod.GET); + } + + @Test + public void requestUri() { + assertUri(request(HttpMethod.GET, URI.create("/uri")), HttpMethod.GET); + } + + @Test + public void fileUploadTemplate() { + assertTemplate(fileUpload("{template}", "t"), HttpMethod.POST); + } + + @Test + public void fileUploadUri() { + assertUri(fileUpload(URI.create("/uri")), HttpMethod.POST); + } + + private void assertTemplate(MockHttpServletRequestBuilder builder, + HttpMethod httpMethod) { + MockHttpServletRequest request = builder.buildRequest(this.servletContext); + assertThat( + (String) request.getAttribute("org.springframework.restdocs.urlTemplate"), + is(equalTo("{template}"))); + assertThat(request.getRequestURI(), is(equalTo("t"))); + assertThat(request.getMethod(), is(equalTo(httpMethod.name()))); + } + + private void assertUri(MockHttpServletRequestBuilder builder, HttpMethod httpMethod) { + MockHttpServletRequest request = builder.buildRequest(this.servletContext); + assertThat(request.getRequestURI(), is(equalTo("/uri"))); + assertThat(request.getMethod(), is(equalTo(httpMethod.name()))); + } +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java index acaff8b9..c0b8f808 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java @@ -19,15 +19,15 @@ package org.springframework.restdocs.curl; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.fileUpload; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.codeBlock; import static org.springframework.restdocs.test.StubMvcResult.result; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import java.io.IOException; @@ -45,7 +45,7 @@ import org.springframework.restdocs.test.ExpectedSnippet; /** * Tests for {@link CurlDocumentation} - * + * * @author Andy Wilkinson * @author Yann Le Guern * @author Dmitriy Mayboroda diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java index 5e9e183c..59b1f547 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java @@ -21,6 +21,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.OK; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.fileUpload; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest; import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse; import static org.springframework.restdocs.snippet.Attributes.attributes; @@ -28,10 +32,6 @@ import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.httpRequest; import static org.springframework.restdocs.test.SnippetMatchers.httpResponse; import static org.springframework.restdocs.test.StubMvcResult.result; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.PUT; @@ -53,7 +53,7 @@ import org.springframework.restdocs.test.ExpectedSnippet; /** * Tests for {@link HttpDocumentation} - * + * * @author Andy Wilkinson * @author Jonathan Pearlin */ diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java index 4c017b04..f23a3ba6 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -27,7 +28,6 @@ import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; import static org.springframework.restdocs.test.StubMvcResult.result; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import java.io.IOException; @@ -45,7 +45,7 @@ import org.springframework.restdocs.test.ExpectedSnippet; /** * Tests for {@link PayloadDocumentation} - * + * * @author Andy Wilkinson */ public class PayloadDocumentationTests { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java index 4aae853d..b193ad5c 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java @@ -20,13 +20,14 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.request.RequestDocumentation.documentPathParameters; import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.restdocs.test.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; import static org.springframework.restdocs.test.StubMvcResult.result; +import static org.springframework.restdocs.test.TestRequestBuilders.get; import java.io.IOException; @@ -56,56 +57,56 @@ public class RequestDocumentationTests { public ExpectedSnippet snippet = new ExpectedSnippet(); @Test - public void undocumentedParameter() throws IOException { + public void undocumentedQueryParameter() throws IOException { this.thrown.expect(SnippetGenerationException.class); this.thrown .expectMessage(equalTo("Query parameters with the following names were" + " not documented: [a]")); - documentQueryParameters("undocumented-parameter", null).handle( + documentQueryParameters("undocumented-query-parameter", null).handle( result(get("/").param("a", "alpha"))); } @Test - public void missingParameter() throws IOException { + public void missingQueryParameter() throws IOException { this.thrown.expect(SnippetGenerationException.class); this.thrown .expectMessage(equalTo("Query parameters with the following names were" + " not found in the request: [a]")); - documentQueryParameters("missing-parameter", null, + documentQueryParameters("missing-query-parameter", null, parameterWithName("a").description("one")).handle(result(get("/"))); } @Test - public void undocumentedParameterAndMissingParameter() throws IOException { + public void undocumentedAndMissingQueryParameters() throws IOException { this.thrown.expect(SnippetGenerationException.class); this.thrown .expectMessage(equalTo("Query parameters with the following names were" + " not documented: [b]. Query parameters with the following" + " names were not found in the request: [a]")); - documentQueryParameters("undocumented-parameter-missing-parameter", null, + documentQueryParameters("undocumented-and-missing-query-parameters", null, parameterWithName("a").description("one")).handle( result(get("/").param("b", "bravo"))); } @Test - public void parameterSnippetFromRequestParameters() throws IOException { - this.snippet.expectQueryParameters("parameter-snippet-request-parameters") + public void queryParameterSnippetFromRequestParameters() throws IOException { + this.snippet.expectQueryParameters("query-parameter-snippet-request-parameters") .withContents( tableWithHeader("Parameter", "Description").row("a", "one").row( "b", "two")); - documentQueryParameters("parameter-snippet-request-parameters", null, + documentQueryParameters("query-parameter-snippet-request-parameters", null, parameterWithName("a").description("one"), parameterWithName("b").description("two")).handle( result(get("/").param("a", "bravo").param("b", "bravo"))); } @Test - public void parameterSnippetFromRequestUriQueryString() throws IOException { - this.snippet.expectQueryParameters("parameter-snippet-request-uri-query-string") - .withContents( - tableWithHeader("Parameter", "Description").row("a", "one").row( - "b", "two")); - documentQueryParameters("parameter-snippet-request-uri-query-string", null, + public void queryParameterSnippetFromRequestUriQueryString() throws IOException { + this.snippet.expectQueryParameters( + "query-parameter-snippet-request-uri-query-string").withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row("b", + "two")); + documentQueryParameters("query-parameter-snippet-request-uri-query-string", null, parameterWithName("a").description("one"), parameterWithName("b").description("two")).handle( result(get("/?a=alpha&b=bravo").requestAttr( @@ -114,12 +115,11 @@ public class RequestDocumentationTests { } @Test - public void parametersWithCustomDescriptorAttributes() throws IOException { - this.snippet - .expectQueryParameters("parameters-with-custom-descriptor-attributes") - .withContents( - tableWithHeader("Parameter", "Description", "Foo").row("a", - "one", "alpha").row("b", "two", "bravo")); + public void queryParametersWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectQueryParameters( + "query-parameters-with-custom-descriptor-attributes").withContents( + tableWithHeader("Parameter", "Description", "Foo").row("a", "one", + "alpha").row("b", "two", "bravo")); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("query-parameters")) .thenReturn( @@ -131,7 +131,7 @@ public class RequestDocumentationTests { request.addParameter("a", "bravo"); request.addParameter("b", "bravo"); documentQueryParameters( - "parameters-with-custom-descriptor-attributes", + "query-parameters-with-custom-descriptor-attributes", null, parameterWithName("a").description("one").attributes( key("foo").value("alpha")), @@ -140,8 +140,8 @@ public class RequestDocumentationTests { } @Test - public void parametersWithCustomAttributes() throws IOException { - this.snippet.expectQueryParameters("parameters-with-custom-attributes") + public void queryParametersWithCustomAttributes() throws IOException { + this.snippet.expectQueryParameters("query-parameters-with-custom-attributes") .withContents(startsWith(".The title")); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("query-parameters")) @@ -154,7 +154,7 @@ public class RequestDocumentationTests { request.addParameter("a", "bravo"); request.addParameter("b", "bravo"); documentQueryParameters( - "parameters-with-custom-attributes", + "query-parameters-with-custom-attributes", attributes(key("title").value("The title")), parameterWithName("a").description("one").attributes( key("foo").value("alpha")), @@ -162,4 +162,91 @@ public class RequestDocumentationTests { key("foo").value("bravo"))).handle(result(request)); } + + @Test + public void undocumentedPathParameter() throws IOException { + this.thrown.expect(SnippetGenerationException.class); + this.thrown.expectMessage(equalTo("Path parameters with the following names were" + + " not documented: [a]")); + documentPathParameters("undocumented-path-parameter", null).handle( + result(get("/{a}/", "alpha"))); + } + + @Test + public void missingPathParameter() throws IOException { + this.thrown.expect(SnippetGenerationException.class); + this.thrown.expectMessage(equalTo("Path parameters with the following names were" + + " not found in the request: [a]")); + documentPathParameters("missing-path-parameter", null, + parameterWithName("a").description("one")).handle(result(get("/"))); + } + + @Test + public void undocumentedAndMissingPathParameters() throws IOException { + this.thrown.expect(SnippetGenerationException.class); + this.thrown.expectMessage(equalTo("Path parameters with the following names were" + + " not documented: [b]. Path parameters with the following" + + " names were not found in the request: [a]")); + documentPathParameters("undocumented-and-missing-path-parameters", null, + parameterWithName("a").description("one")).handle( + result(get("/{b}", "bravo"))); + } + + @Test + public void pathParameters() throws IOException { + this.snippet.expectPathParameters("path-parameters").withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row("b", + "two")); + documentPathParameters("path-parameters", null, + parameterWithName("a").description("one"), + parameterWithName("b").description("two")).handle( + result(get("/{a}/{b}", "alpha", "banana").requestAttr( + RestDocumentationContext.class.getName(), + new RestDocumentationContext()))); + } + + @Test + public void pathParametersWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectPathParameters( + "path-parameters-with-custom-descriptor-attributes").withContents( + tableWithHeader("Parameter", "Description", "Foo").row("a", "one", + "alpha").row("b", "two", "bravo")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("path-parameters")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/path-parameters-with-extra-column.snippet")); + documentPathParameters( + "path-parameters-with-custom-descriptor-attributes", + null, + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), + parameterWithName("b").description("two").attributes( + key("foo").value("bravo"))).handle( + result(get("{a}/{b}", "alpha", "bravo").requestAttr( + TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)))); + } + + @Test + public void pathParametersWithCustomAttributes() throws IOException { + this.snippet.expectPathParameters("path-parameters-with-custom-attributes") + .withContents(startsWith(".The title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("path-parameters")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/path-parameters-with-title.snippet")); + documentPathParameters( + "path-parameters-with-custom-attributes", + attributes(key("title").value("The title")), + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), + parameterWithName("b").description("two").attributes( + key("foo").value("bravo"))).handle( + result(get("{a}/{b}", "alpha", "bravo").requestAttr( + TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)))); + + } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java index 1bc2dda0..5847424c 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java @@ -135,6 +135,11 @@ public class ExpectedSnippet implements TestRule { return this; } + public ExpectedSnippet expectPathParameters(String name) { + expect(name, "path-parameters"); + return this; + } + private ExpectedSnippet expect(String name, String type) { this.expectedName = name; this.expectedType = type; diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/RestDocumentationRequestBuilders.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/RestDocumentationRequestBuilders.java deleted file mode 100644 index 05bcd7b4..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/RestDocumentationRequestBuilders.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.springframework.restdocs.test; - -import org.springframework.restdocs.config.RestDocumentationContext; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -public class RestDocumentationRequestBuilders { - - private RestDocumentationRequestBuilders() { - - } - - public static MockHttpServletRequestBuilder get(String urlTemplate) { - return MockMvcRequestBuilders.get(urlTemplate).requestAttr( - RestDocumentationContext.class.getName(), new RestDocumentationContext()); - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java new file mode 100644 index 00000000..de6d5df0 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-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.restdocs.test; + +import org.springframework.restdocs.config.RestDocumentationContext; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +public class TestRequestBuilders { + + private TestRequestBuilders() { + + } + + public static MockHttpServletRequestBuilder get(String urlTemplate, + Object... urlVariables) { + return org.springframework.restdocs.RestDocumentationRequestBuilders.get( + urlTemplate, urlVariables).requestAttr( + RestDocumentationContext.class.getName(), new RestDocumentationContext()); + } + +} diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/path-parameters-with-extra-column.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/path-parameters-with-extra-column.snippet new file mode 100644 index 00000000..fb97f89b --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/path-parameters-with-extra-column.snippet @@ -0,0 +1,10 @@ +|=== +|Parameter|Description|Foo + +{{#parameters}} +|{{name}} +|{{description}} +|{{foo}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/path-parameters-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/path-parameters-with-title.snippet new file mode 100644 index 00000000..611254aa --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/path-parameters-with-title.snippet @@ -0,0 +1,10 @@ +.{{title}} +|=== +|Parameter|Description + +{{#parameters}} +|{{name}} +|{{description}} + +{{/parameters}} +|=== \ No newline at end of file