Rework the API to improve readability and extensibility
This commit updates the API to improve its extensibility and
readability.
SnippetWritingResultHandler has been replaced with a more general
purpose Snippet interface. Snippets are now provided to the main
document method using varargs rather than the various with… methods
that were previously used. As a result a custom Snippet implementation
can now be used in exactly the same way as any of the built-in
snippets:
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(document("index-example",
links(
linkWithRel("notes").description("…"),
linkWithRel("tags").description("…")),
responseFields(
fieldWithPath("_links").description("…")),
yourCustomSnippet()));
Control of the snippets that are generated by default is now available
via RestDocumentationConfigurer:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration().snippets()
.withDefaults(curlRequest(), yourCustomSnippet()))
.build();
See gh-73
This commit is contained in:
@@ -48,6 +48,26 @@ include::{examples-dir}/com/example/CustomEncoding.java[tags=custom-encoding]
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[configuration-default-snippets]]
|
||||||
|
=== Default snippets
|
||||||
|
|
||||||
|
Three snippets are produced by default:
|
||||||
|
|
||||||
|
- `curl-request`
|
||||||
|
- `http-request`
|
||||||
|
- `http-response`
|
||||||
|
|
||||||
|
This default configuration is applied by `RestDocumentationConfigurer`. You can use its
|
||||||
|
API to change the configuration. For example, to only produce the `curl-request` snippet
|
||||||
|
by default:
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::{examples-dir}/com/example/CustomDefaultSnippetsConfiguration.java[tags=custom-default-snippets]
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[configuration-output-directory]]
|
[[configuration-output-directory]]
|
||||||
=== Snippet output directory
|
=== Snippet output directory
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ https://en.wikipedia.org/wiki/HATEOAS[Hypermedia-based] API:
|
|||||||
----
|
----
|
||||||
include::{examples-dir}/com/example/Hypermedia.java[tag=links]
|
include::{examples-dir}/com/example/Hypermedia.java[tag=links]
|
||||||
----
|
----
|
||||||
<1> Use `withLinks` is to describe the expected links.
|
<1> Produce a snippet describing the response's links. Uses the static `links` method on
|
||||||
|
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
|
||||||
<2> Expect a link whose rel is `alpha`. Uses the static `linkWithRel` method on
|
<2> Expect a link whose rel is `alpha`. Uses the static `linkWithRel` method on
|
||||||
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
|
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
|
||||||
<3> Expect a link whose rel is `bravo`.
|
<3> Expect a link whose rel is `bravo`.
|
||||||
@@ -40,14 +41,14 @@ Two link formats are understood by default:
|
|||||||
content type of the response is compatible with `application/hal+json`.
|
content type of the response is compatible with `application/hal+json`.
|
||||||
|
|
||||||
If you are using Atom or HAL-format links but with a different content type you can
|
If you are using Atom or HAL-format links but with a different content type you can
|
||||||
provide one of the built-in `LinkExtractor` implementations to `withLinks`. For example:
|
provide one of the built-in `LinkExtractor` implementations to `links`. For example:
|
||||||
|
|
||||||
[source,java,indent=0]
|
[source,java,indent=0]
|
||||||
----
|
----
|
||||||
include::{examples-dir}/com/example/Hypermedia.java[tag=explicit-extractor]
|
include::{examples-dir}/com/example/Hypermedia.java[tag=explicit-extractor]
|
||||||
----
|
----
|
||||||
<1> Indicate that the links are in HAL format. Uses the static `halLinks` method on
|
<1> Indicate that the links are in HAL format. Uses the static `halLinks` method on
|
||||||
`org.springframework.restdocs.hypermedia.LinkExtractors`.
|
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
|
||||||
|
|
||||||
If your API represents its links in a format other than Atom or HAL you can provide your
|
If your API represents its links in a format other than Atom or HAL you can provide your
|
||||||
own implementation of the `LinkExtractor` interface to extract the links from the
|
own implementation of the `LinkExtractor` interface to extract the links from the
|
||||||
@@ -176,13 +177,14 @@ include::{examples-dir}/com/example/Payload.java[tags=explicit-type]
|
|||||||
[[documenting-your-api-query-parameters]]
|
[[documenting-your-api-query-parameters]]
|
||||||
=== Query parameters
|
=== Query parameters
|
||||||
|
|
||||||
A request's query parameters can be documented using `withQueryParameters`
|
A request's query parameters can be documented using `queryParameters`
|
||||||
|
|
||||||
[source,java,indent=0]
|
[source,java,indent=0]
|
||||||
----
|
----
|
||||||
include::{examples-dir}/com/example/QueryParameters.java[tags=query-parameters]
|
include::{examples-dir}/com/example/QueryParameters.java[tags=query-parameters]
|
||||||
----
|
----
|
||||||
<1> Use `withQueryParameters` to describe the query parameters.
|
<1> Produce a snippet describing the request's query parameters. Uses the static
|
||||||
|
`queryParameters` method on `org.springframework.restdocs.request.RequestDocumentation`.
|
||||||
<2> Document a parameter named `page`. Uses the static `parameterWithName` method on
|
<2> Document a parameter named `page`. Uses the static `parameterWithName` method on
|
||||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||||
<3> Document a parameter named `per_page`.
|
<3> Document a parameter named `per_page`.
|
||||||
@@ -199,14 +201,15 @@ is not found in the request.
|
|||||||
[[documenting-your-api-path-parameters]]
|
[[documenting-your-api-path-parameters]]
|
||||||
=== Path parameters
|
=== Path parameters
|
||||||
|
|
||||||
A request's path parameters can be documented using `withPathParameters`
|
A request's path parameters can be documented using `pathParameters`
|
||||||
|
|
||||||
[source,java,indent=0]
|
[source,java,indent=0]
|
||||||
----
|
----
|
||||||
include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters]
|
include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters]
|
||||||
----
|
----
|
||||||
<1> Build the request. Uses the static `get` method on `RestDocumentationRequestBuilders`.
|
<1> Build the request. Uses the static `get` method on `RestDocumentationRequestBuilders`.
|
||||||
<2> Use `withPathParameters` to describe the path parameters.
|
<2> Produce a snippet describing the request's path parameters. Uses the static
|
||||||
|
`pathParameters` method on `org.springframework.restdocs.request.RequestDocumentation`.
|
||||||
<3> Document a parameter named `longitude`. Uses the static `parameterWithName` method on
|
<3> Document a parameter named `longitude`. Uses the static `parameterWithName` method on
|
||||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||||
<4> Document a parameter named `latitude`.
|
<4> Document a parameter named `latitude`.
|
||||||
@@ -244,6 +247,9 @@ documented
|
|||||||
| Contains the HTTP response that was returned
|
| Contains the HTTP response that was returned
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
You can configure which snippets are produced by default. Please refer to the
|
||||||
|
<<configuration, configuration section>> for more information.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[documentating-your-api-parameterized-output-directories]]
|
[[documentating-your-api-parameterized-output-directories]]
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ be included in the project's jar:
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>copy-resources</goal>
|
<goal>copy-resources</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<outputDirectory>
|
<outputDirectory>
|
||||||
${project.build.outputDirectory}/static/docs
|
${project.build.outputDirectory}/static/docs
|
||||||
</outputDirectory>
|
</outputDirectory>
|
||||||
@@ -245,7 +245,7 @@ The `MockMvc` instance is configured using a `RestDocumentationConfigurer`. An i
|
|||||||
of this class can be obtained from the static `documentationConfiguration()` method on
|
of this class can be obtained from the static `documentationConfiguration()` method on
|
||||||
`org.springframework.restdocs.RestDocumentation`. `RestDocumentationConfigurer` applies
|
`org.springframework.restdocs.RestDocumentation`. `RestDocumentationConfigurer` applies
|
||||||
sensible defaults and also provides an API for customizing the configuration. Refer to the
|
sensible defaults and also provides an API for customizing the configuration. Refer to the
|
||||||
<<configuration, Configuration section>> for more information.
|
<<configuration, configuration section>> for more information.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.example;
|
||||||
|
|
||||||
|
import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
|
||||||
|
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
|
||||||
|
public class CustomDefaultSnippetsConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebApplicationContext context;
|
||||||
|
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
// tag::custom-default-snippets[]
|
||||||
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||||
|
.apply(documentationConfiguration().snippets()
|
||||||
|
.withDefaults(curlRequest()))
|
||||||
|
.build();
|
||||||
|
// end::custom-default-snippets[]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ import static org.springframework.restdocs.RestDocumentation.document;
|
|||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
||||||
import static org.springframework.restdocs.hypermedia.LinkExtractors.halLinks;
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
|
||||||
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.halLinks;
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
@@ -29,13 +30,13 @@ public class Hypermedia {
|
|||||||
|
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
public void links() throws Exception {
|
public void defaultExtractor() throws Exception {
|
||||||
// tag::links[]
|
// tag::links[]
|
||||||
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("index").withLinks( // <1>
|
.andDo(document("index", links( // <1>
|
||||||
linkWithRel("alpha").description("Link to the alpha resource"), // <2>
|
linkWithRel("alpha").description("Link to the alpha resource"), // <2>
|
||||||
linkWithRel("bravo").description("Link to the bravo resource"))); // <3>
|
linkWithRel("bravo").description("Link to the bravo resource")))); // <3>
|
||||||
// end::links[]
|
// end::links[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,9 +44,9 @@ public class Hypermedia {
|
|||||||
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
//tag::explicit-extractor[]
|
//tag::explicit-extractor[]
|
||||||
.andDo(document("index").withLinks(halLinks(), // <1>
|
.andDo(document("index", links(halLinks(), // <1>
|
||||||
linkWithRel("alpha").description("Link to the alpha resource"),
|
linkWithRel("alpha").description("Link to the alpha resource"),
|
||||||
linkWithRel("bravo").description("Link to the bravo resource")));
|
linkWithRel("bravo").description("Link to the bravo resource"))));
|
||||||
// end::explicit-extractor[]
|
// end::explicit-extractor[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.example;
|
|||||||
|
|
||||||
import static org.springframework.restdocs.RestDocumentation.document;
|
import static org.springframework.restdocs.RestDocumentation.document;
|
||||||
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
@@ -27,14 +28,14 @@ public class PathParameters {
|
|||||||
|
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
public void pathParameters() throws Exception {
|
public void pathParametersSnippet() throws Exception {
|
||||||
// tag::path-parameters[]
|
// tag::path-parameters[]
|
||||||
this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) // <1>
|
this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) // <1>
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("locations").withPathParameters( // <2>
|
.andDo(document("locations", pathParameters( // <2>
|
||||||
parameterWithName("latitude").description("The location's latitude"), // <3>
|
parameterWithName("latitude").description("The location's latitude"), // <3>
|
||||||
parameterWithName("longitude").description("The location's longitude") // <4>
|
parameterWithName("longitude").description("The location's longitude") // <4>
|
||||||
));
|
)));
|
||||||
// end::path-parameters[]
|
// end::path-parameters[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package com.example;
|
|||||||
|
|
||||||
import static org.springframework.restdocs.RestDocumentation.document;
|
import static org.springframework.restdocs.RestDocumentation.document;
|
||||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
@@ -26,7 +28,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.restdocs.payload.FieldType;
|
import org.springframework.restdocs.payload.FieldType;
|
||||||
import org.springframework.restdocs.snippet.Attributes;
|
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
public class Payload {
|
public class Payload {
|
||||||
@@ -37,9 +38,9 @@ private MockMvc mockMvc;
|
|||||||
// tag::response[]
|
// tag::response[]
|
||||||
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("index").withResponseFields( // <1>
|
.andDo(document("index", responseFields( // <1>
|
||||||
fieldWithPath("contact").description("The user's contact details"), // <2>
|
fieldWithPath("contact").description("The user's contact details"), // <2>
|
||||||
fieldWithPath("contact.email").description("The user's email address"))); // <3>
|
fieldWithPath("contact.email").description("The user's email address")))); // <3>
|
||||||
// end::response[]
|
// end::response[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +48,11 @@ private MockMvc mockMvc;
|
|||||||
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
// tag::explicit-type[]
|
// tag::explicit-type[]
|
||||||
.andDo(document("index").withResponseFields(
|
.andDo(document("index", responseFields(
|
||||||
fieldWithPath("contact.email")
|
fieldWithPath("contact.email")
|
||||||
.type(FieldType.STRING) // <1>
|
.type(FieldType.STRING) // <1>
|
||||||
.optional()
|
.optional()
|
||||||
.description("The user's email address")));
|
.description("The user's email address"))));
|
||||||
// end::explicit-type[]
|
// end::explicit-type[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ private MockMvc mockMvc;
|
|||||||
this.mockMvc.perform(post("/users/").accept(MediaType.APPLICATION_JSON))
|
this.mockMvc.perform(post("/users/").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
// tag::constraints[]
|
// tag::constraints[]
|
||||||
.andDo(document("create-user").withRequestFields(
|
.andDo(document("create-user", requestFields(
|
||||||
attributes(
|
attributes(
|
||||||
key("title").value("Fields for user creation")), // <1>
|
key("title").value("Fields for user creation")), // <1>
|
||||||
fieldWithPath("name")
|
fieldWithPath("name")
|
||||||
@@ -69,7 +70,7 @@ private MockMvc mockMvc;
|
|||||||
fieldWithPath("email")
|
fieldWithPath("email")
|
||||||
.description("The user's email address")
|
.description("The user's email address")
|
||||||
.attributes(
|
.attributes(
|
||||||
key("constraints").value("Must be a valid email address")))); // <3>
|
key("constraints").value("Must be a valid email address"))))); // <3>
|
||||||
// end::constraints[]
|
// end::constraints[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.example;
|
|||||||
|
|
||||||
import static org.springframework.restdocs.RestDocumentation.document;
|
import static org.springframework.restdocs.RestDocumentation.document;
|
||||||
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
@@ -27,14 +28,14 @@ public class QueryParameters {
|
|||||||
|
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
public void queryParameters() throws Exception {
|
public void queryParametersSnippet() throws Exception {
|
||||||
// tag::query-parameters[]
|
// tag::query-parameters[]
|
||||||
this.mockMvc.perform(get("/users?page=2&per_page=100"))
|
this.mockMvc.perform(get("/users?page=2&per_page=100"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("users").withQueryParameters( // <1>
|
.andDo(document("users", queryParameters( // <1>
|
||||||
parameterWithName("page").description("The page to retrieve"), // <2>
|
parameterWithName("page").description("The page to retrieve"), // <2>
|
||||||
parameterWithName("per_page").description("Entries per page") // <3>
|
parameterWithName("per_page").description("Entries per page") // <3>
|
||||||
));
|
)));
|
||||||
// end::query-parameters[]
|
// end::query-parameters[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ import static org.hamcrest.Matchers.notNullValue;
|
|||||||
import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
|
import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
|
||||||
import static org.springframework.restdocs.RestDocumentation.document;
|
import static org.springframework.restdocs.RestDocumentation.document;
|
||||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
||||||
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
|
||||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||||
@@ -100,26 +103,26 @@ public class ApiDocumentation {
|
|||||||
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
||||||
.andExpect(jsonPath("status", is(400)))
|
.andExpect(jsonPath("status", is(400)))
|
||||||
.andExpect(jsonPath("path", is(notNullValue())))
|
.andExpect(jsonPath("path", is(notNullValue())))
|
||||||
.andDo(document("error-example")
|
.andDo(document("error-example",
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
|
fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
|
||||||
fieldWithPath("message").description("A description of the cause of the error"),
|
fieldWithPath("message").description("A description of the cause of the error"),
|
||||||
fieldWithPath("path").description("The path to which the request was made"),
|
fieldWithPath("path").description("The path to which the request was made"),
|
||||||
fieldWithPath("status").description("The HTTP status code, e.g. `400`"),
|
fieldWithPath("status").description("The HTTP status code, e.g. `400`"),
|
||||||
fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred")));
|
fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void indexExample() throws Exception {
|
public void indexExample() throws Exception {
|
||||||
this.mockMvc.perform(get("/"))
|
this.mockMvc.perform(get("/"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("index-example")
|
.andDo(document("index-example",
|
||||||
.withLinks(
|
links(
|
||||||
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
||||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>"),
|
linkWithRel("tags").description("The <<resources-tags,Tags resource>>"),
|
||||||
linkWithRel("profile").description("The ALPS profile for the service"))
|
linkWithRel("profile").description("The ALPS profile for the service")),
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources")));
|
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,9 +138,9 @@ public class ApiDocumentation {
|
|||||||
|
|
||||||
this.mockMvc.perform(get("/notes"))
|
this.mockMvc.perform(get("/notes"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("notes-list-example")
|
.andDo(document("notes-list-example",
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>")));
|
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,11 +164,11 @@ public class ApiDocumentation {
|
|||||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(note))).andExpect(
|
this.objectMapper.writeValueAsString(note))).andExpect(
|
||||||
status().isCreated())
|
status().isCreated())
|
||||||
.andDo(document("notes-create-example")
|
.andDo(document("notes-create-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("title").description("The title of the note"),
|
fieldWithPath("title").description("The title of the note"),
|
||||||
fieldWithPath("body").description("The body of the note"),
|
fieldWithPath("body").description("The body of the note"),
|
||||||
fieldWithPath("tags").description("An array of tag resource URIs")));
|
fieldWithPath("tags").description("An array of tag resource URIs"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -198,14 +201,14 @@ public class ApiDocumentation {
|
|||||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||||
.andExpect(jsonPath("_links.tags", is(notNullValue())))
|
.andExpect(jsonPath("_links.tags", is(notNullValue())))
|
||||||
.andDo(document("note-get-example")
|
.andDo(document("note-get-example",
|
||||||
.withLinks(
|
links(
|
||||||
linkWithRel("self").description("This <<resources-note,note>>"),
|
linkWithRel("self").description("This <<resources-note,note>>"),
|
||||||
linkWithRel("tags").description("This note's tags"))
|
linkWithRel("tags").description("This note's tags")),
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("title").description("The title of the note"),
|
fieldWithPath("title").description("The title of the note"),
|
||||||
fieldWithPath("body").description("The body of the note"),
|
fieldWithPath("body").description("The body of the note"),
|
||||||
fieldWithPath("_links").description("<<resources-note-links,Links>> to other resources")));
|
fieldWithPath("_links").description("<<resources-note-links,Links>> to other resources"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -219,9 +222,9 @@ public class ApiDocumentation {
|
|||||||
|
|
||||||
this.mockMvc.perform(get("/tags"))
|
this.mockMvc.perform(get("/tags"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("tags-list-example")
|
.andDo(document("tags-list-example",
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>")));
|
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -233,9 +236,9 @@ public class ApiDocumentation {
|
|||||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(tag)))
|
this.objectMapper.writeValueAsString(tag)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andDo(document("tags-create-example")
|
.andDo(document("tags-create-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("name").description("The name of the tag")));
|
fieldWithPath("name").description("The name of the tag"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -274,12 +277,11 @@ public class ApiDocumentation {
|
|||||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(noteUpdate)))
|
this.objectMapper.writeValueAsString(noteUpdate)))
|
||||||
.andExpect(status().isNoContent())
|
.andExpect(status().isNoContent())
|
||||||
.andDo(document("note-update-example")
|
.andDo(document("note-update-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(),
|
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("body").description("The body of the note").type(FieldType.STRING).optional(),
|
||||||
fieldWithPath("tags").description("An array of tag resource URIs").optional()));
|
fieldWithPath("tags").description("An array of tag resource URIs").optional())));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -297,13 +299,13 @@ public class ApiDocumentation {
|
|||||||
this.mockMvc.perform(get(tagLocation))
|
this.mockMvc.perform(get(tagLocation))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("name", is(tag.get("name"))))
|
.andExpect(jsonPath("name", is(tag.get("name"))))
|
||||||
.andDo(document("tag-get-example")
|
.andDo(document("tag-get-example",
|
||||||
.withLinks(
|
links(
|
||||||
linkWithRel("self").description("This <<resources-tag,tag>>"),
|
linkWithRel("self").description("This <<resources-tag,tag>>"),
|
||||||
linkWithRel("notes").description("The <<resources-tagged-notes,notes>> that have this tag"))
|
linkWithRel("notes").description("The <<resources-tagged-notes,notes>> that have this tag")),
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("name").description("The name of the tag"),
|
fieldWithPath("name").description("The name of the tag"),
|
||||||
fieldWithPath("_links").description("<<resources-tag-links,Links>> to other resources")));
|
fieldWithPath("_links").description("<<resources-tag-links,Links>> to other resources"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -325,9 +327,9 @@ public class ApiDocumentation {
|
|||||||
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(tagUpdate)))
|
this.objectMapper.writeValueAsString(tagUpdate)))
|
||||||
.andExpect(status().isNoContent())
|
.andExpect(status().isNoContent())
|
||||||
.andDo(document("tag-update-example")
|
.andDo(document("tag-update-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("name").description("The name of the tag")));
|
fieldWithPath("name").description("The name of the tag"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNote(String title, String body) {
|
private void createNote(String title, String body) {
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ import static org.hamcrest.Matchers.notNullValue;
|
|||||||
import static org.springframework.restdocs.RestDocumentation.document;
|
import static org.springframework.restdocs.RestDocumentation.document;
|
||||||
import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
|
import static org.springframework.restdocs.RestDocumentation.documentationConfiguration;
|
||||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
||||||
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
|
||||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||||
@@ -89,25 +92,25 @@ public class ApiDocumentation {
|
|||||||
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
||||||
.andExpect(jsonPath("status", is(400)))
|
.andExpect(jsonPath("status", is(400)))
|
||||||
.andExpect(jsonPath("path", is(notNullValue())))
|
.andExpect(jsonPath("path", is(notNullValue())))
|
||||||
.andDo(document("error-example")
|
.andDo(document("error-example",
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
|
fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
|
||||||
fieldWithPath("message").description("A description of the cause of the error"),
|
fieldWithPath("message").description("A description of the cause of the error"),
|
||||||
fieldWithPath("path").description("The path to which the request was made"),
|
fieldWithPath("path").description("The path to which the request was made"),
|
||||||
fieldWithPath("status").description("The HTTP status code, e.g. `400`"),
|
fieldWithPath("status").description("The HTTP status code, e.g. `400`"),
|
||||||
fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred")));
|
fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void indexExample() throws Exception {
|
public void indexExample() throws Exception {
|
||||||
this.mockMvc.perform(get("/"))
|
this.mockMvc.perform(get("/"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("index-example")
|
.andDo(document("index-example",
|
||||||
.withLinks(
|
links(
|
||||||
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
||||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>"))
|
linkWithRel("tags").description("The <<resources-tags,Tags resource>>")),
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources")));
|
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -122,9 +125,9 @@ public class ApiDocumentation {
|
|||||||
|
|
||||||
this.mockMvc.perform(get("/notes"))
|
this.mockMvc.perform(get("/notes"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("notes-list-example")
|
.andDo(document("notes-list-example",
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>")));
|
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -148,11 +151,11 @@ public class ApiDocumentation {
|
|||||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(note)))
|
this.objectMapper.writeValueAsString(note)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andDo(document("notes-create-example")
|
.andDo(document("notes-create-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("title").description("The title of the note"),
|
fieldWithPath("title").description("The title of the note"),
|
||||||
fieldWithPath("body").description("The body of the note"),
|
fieldWithPath("body").description("The body of the note"),
|
||||||
fieldWithPath("tags").description("An array of tag resource URIs")));
|
fieldWithPath("tags").description("An array of tag resource URIs"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -185,14 +188,14 @@ public class ApiDocumentation {
|
|||||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||||
.andExpect(jsonPath("_links.note-tags", is(notNullValue())))
|
.andExpect(jsonPath("_links.note-tags", is(notNullValue())))
|
||||||
.andDo(document("note-get-example")
|
.andDo(document("note-get-example",
|
||||||
.withLinks(
|
links(
|
||||||
linkWithRel("self").description("This <<resources-note,note>>"),
|
linkWithRel("self").description("This <<resources-note,note>>"),
|
||||||
linkWithRel("note-tags").description("This note's <<resources-note-tags,tags>>"))
|
linkWithRel("note-tags").description("This note's <<resources-note-tags,tags>>")),
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("title").description("The title of the note"),
|
fieldWithPath("title").description("The title of the note"),
|
||||||
fieldWithPath("body").description("The body of the note"),
|
fieldWithPath("body").description("The body of the note"),
|
||||||
fieldWithPath("_links").description("<<resources-note-links,Links>> to other resources")));
|
fieldWithPath("_links").description("<<resources-note-links,Links>> to other resources"))));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,9 +210,9 @@ public class ApiDocumentation {
|
|||||||
|
|
||||||
this.mockMvc.perform(get("/tags"))
|
this.mockMvc.perform(get("/tags"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("tags-list-example")
|
.andDo(document("tags-list-example",
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>")));
|
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -221,9 +224,9 @@ public class ApiDocumentation {
|
|||||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(tag)))
|
this.objectMapper.writeValueAsString(tag)))
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andDo(document("tags-create-example")
|
.andDo(document("tags-create-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("name").description("The name of the tag")));
|
fieldWithPath("name").description("The name of the tag"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -262,11 +265,11 @@ public class ApiDocumentation {
|
|||||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(noteUpdate)))
|
this.objectMapper.writeValueAsString(noteUpdate)))
|
||||||
.andExpect(status().isNoContent())
|
.andExpect(status().isNoContent())
|
||||||
.andDo(document("note-update-example")
|
.andDo(document("note-update-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(),
|
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("body").description("The body of the note").type(FieldType.STRING).optional(),
|
||||||
fieldWithPath("tags").description("An array of tag resource URIs").optional()));
|
fieldWithPath("tags").description("An array of tag resource URIs").optional())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -284,13 +287,13 @@ public class ApiDocumentation {
|
|||||||
this.mockMvc.perform(get(tagLocation))
|
this.mockMvc.perform(get(tagLocation))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("name", is(tag.get("name"))))
|
.andExpect(jsonPath("name", is(tag.get("name"))))
|
||||||
.andDo(document("tag-get-example")
|
.andDo(document("tag-get-example",
|
||||||
.withLinks(
|
links(
|
||||||
linkWithRel("self").description("This <<resources-tag,tag>>"),
|
linkWithRel("self").description("This <<resources-tag,tag>>"),
|
||||||
linkWithRel("tagged-notes").description("The <<resources-tagged-notes,notes>> that have this tag"))
|
linkWithRel("tagged-notes").description("The <<resources-tagged-notes,notes>> that have this tag")),
|
||||||
.withResponseFields(
|
responseFields(
|
||||||
fieldWithPath("name").description("The name of the tag"),
|
fieldWithPath("name").description("The name of the tag"),
|
||||||
fieldWithPath("_links").description("<<resources-tag-links,Links>> to other resources")));
|
fieldWithPath("_links").description("<<resources-tag-links,Links>> to other resources"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -312,9 +315,9 @@ public class ApiDocumentation {
|
|||||||
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||||
this.objectMapper.writeValueAsString(tagUpdate)))
|
this.objectMapper.writeValueAsString(tagUpdate)))
|
||||||
.andExpect(status().isNoContent())
|
.andExpect(status().isNoContent())
|
||||||
.andDo(document("tag-update-example")
|
.andDo(document("tag-update-example",
|
||||||
.withRequestFields(
|
requestFields(
|
||||||
fieldWithPath("name").description("The name of the tag")));
|
fieldWithPath("name").description("The name of the tag"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNote(String title, String body) {
|
private void createNote(String title, String body) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.springframework.cglib.proxy.MethodProxy;
|
|||||||
import org.springframework.core.BridgeMethodResolver;
|
import org.springframework.core.BridgeMethodResolver;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.restdocs.response.ResponsePostProcessor;
|
import org.springframework.restdocs.response.ResponsePostProcessor;
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
@@ -47,19 +48,22 @@ public final class ResponseModifier {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a {@link RestDocumentationResultHandler} that can be used to document the
|
* Provides a {@link RestDocumentationResultHandler} that can be used to document the
|
||||||
* request and modified result.
|
* request and modified response.
|
||||||
* @param identifier An identifier for the API call that is being documented
|
* @param identifier an identifier for the API call that is being documented
|
||||||
|
* @param snippets the snippets to use to document the call
|
||||||
* @return the result handler that will produce the documentation
|
* @return the result handler that will produce the documentation
|
||||||
*/
|
*/
|
||||||
public RestDocumentationResultHandler andDocument(String identifier) {
|
public RestDocumentationResultHandler andDocument(String identifier,
|
||||||
return new ResponseModifyingRestDocumentationResultHandler(identifier);
|
Snippet... snippets) {
|
||||||
|
return new ResponseModifyingRestDocumentationResultHandler(identifier, snippets);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResponseModifyingRestDocumentationResultHandler extends
|
class ResponseModifyingRestDocumentationResultHandler extends
|
||||||
RestDocumentationResultHandler {
|
RestDocumentationResultHandler {
|
||||||
|
|
||||||
public ResponseModifyingRestDocumentationResultHandler(String identifier) {
|
private ResponseModifyingRestDocumentationResultHandler(String identifier,
|
||||||
super(identifier);
|
Snippet... snippets) {
|
||||||
|
super(identifier, snippets);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.springframework.restdocs;
|
|||||||
import org.springframework.restdocs.config.RestDocumentationConfigurer;
|
import org.springframework.restdocs.config.RestDocumentationConfigurer;
|
||||||
import org.springframework.restdocs.response.ResponsePostProcessor;
|
import org.springframework.restdocs.response.ResponsePostProcessor;
|
||||||
import org.springframework.restdocs.response.ResponsePostProcessors;
|
import org.springframework.restdocs.response.ResponsePostProcessors;
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.test.web.servlet.ResultActions;
|
import org.springframework.test.web.servlet.ResultActions;
|
||||||
@@ -39,6 +40,7 @@ public abstract class RestDocumentation {
|
|||||||
/**
|
/**
|
||||||
* Provides access to a {@link MockMvcConfigurer} that can be used to configure the
|
* Provides access to a {@link MockMvcConfigurer} that can be used to configure the
|
||||||
* REST documentation when building a {@link MockMvc} instance.
|
* REST documentation when building a {@link MockMvc} instance.
|
||||||
|
*
|
||||||
* @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)
|
* @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)
|
||||||
* @return the configurer
|
* @return the configurer
|
||||||
*/
|
*/
|
||||||
@@ -47,15 +49,18 @@ public abstract class RestDocumentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Documents the API call using the given {@code identifier}.
|
* Documents the API call with the given {@code identifier} using the given
|
||||||
|
* {@code handlers}.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
* @param identifier an identifier for the API call that is being documented
|
||||||
|
* @param snippets the snippets that will document the API call
|
||||||
* @return a Mock MVC {@code ResultHandler} that will produce the documentation
|
* @return a Mock MVC {@code ResultHandler} that will produce the documentation
|
||||||
* @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder)
|
* @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder)
|
||||||
* @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler)
|
* @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler)
|
||||||
*/
|
*/
|
||||||
public static RestDocumentationResultHandler document(String identifier) {
|
public static RestDocumentationResultHandler document(String identifier,
|
||||||
return new RestDocumentationResultHandler(identifier);
|
Snippet... snippets) {
|
||||||
|
return new RestDocumentationResultHandler(identifier, snippets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.springframework.restdocs;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.restdocs.request.RequestDocumentation;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
@@ -26,13 +27,13 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
|||||||
/**
|
/**
|
||||||
* A drop-in replacement for {@link MockMvcRequestBuilders} that captures a request's URL
|
* A drop-in replacement for {@link MockMvcRequestBuilders} that captures a request's URL
|
||||||
* template and makes it available for documentation. Required when
|
* template and makes it available for documentation. Required when
|
||||||
* {@link RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs .request.ParameterDescriptor...)
|
* {@link RequestDocumentation#pathParameters(org.springframework.restdocs.request.ParameterDescriptor...)
|
||||||
* documenting path parameters} and recommended for general usage.
|
* ) documenting path parameters} and recommended for general usage.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @see MockMvcRequestBuilders
|
* @see MockMvcRequestBuilders
|
||||||
* @see RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs.request.ParameterDescriptor...)
|
* @see RequestDocumentation#pathParameters(org.springframework.restdocs.request.ParameterDescriptor...)
|
||||||
* @see RestDocumentationResultHandler#withPathParameters(java.util.Map,
|
* @see RequestDocumentation#pathParameters(java.util.Map,
|
||||||
* org.springframework.restdocs.request.ParameterDescriptor...)
|
* org.springframework.restdocs.request.ParameterDescriptor...)
|
||||||
*/
|
*/
|
||||||
public abstract class RestDocumentationRequestBuilders {
|
public abstract class RestDocumentationRequestBuilders {
|
||||||
|
|||||||
@@ -16,28 +16,11 @@
|
|||||||
|
|
||||||
package org.springframework.restdocs;
|
package org.springframework.restdocs;
|
||||||
|
|
||||||
import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest;
|
|
||||||
import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest;
|
|
||||||
import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse;
|
|
||||||
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;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.restdocs.hypermedia.HypermediaDocumentation;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.hypermedia.LinkDescriptor;
|
|
||||||
import org.springframework.restdocs.hypermedia.LinkExtractor;
|
|
||||||
import org.springframework.restdocs.hypermedia.LinkExtractors;
|
|
||||||
import org.springframework.restdocs.payload.FieldDescriptor;
|
|
||||||
import org.springframework.restdocs.payload.PayloadDocumentation;
|
|
||||||
import org.springframework.restdocs.request.ParameterDescriptor;
|
|
||||||
import org.springframework.restdocs.request.RequestDocumentation;
|
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.test.web.servlet.ResultHandler;
|
import org.springframework.test.web.servlet.ResultHandler;
|
||||||
|
|
||||||
@@ -46,317 +29,32 @@ import org.springframework.test.web.servlet.ResultHandler;
|
|||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Andreas Evers
|
* @author Andreas Evers
|
||||||
* @see RestDocumentation#document(String)
|
* @see RestDocumentation#document(String, Snippet...)
|
||||||
*/
|
*/
|
||||||
public class RestDocumentationResultHandler implements ResultHandler {
|
public class RestDocumentationResultHandler implements ResultHandler {
|
||||||
|
|
||||||
private final String identifier;
|
private final String identifier;
|
||||||
|
|
||||||
private SnippetWritingResultHandler curlRequest;
|
private final List<Snippet> snippets;
|
||||||
|
|
||||||
private SnippetWritingResultHandler httpRequest;
|
RestDocumentationResultHandler(String identifier, Snippet... snippets) {
|
||||||
|
|
||||||
private SnippetWritingResultHandler httpResponse;
|
|
||||||
|
|
||||||
private List<SnippetWritingResultHandler> delegates = new ArrayList<>();
|
|
||||||
|
|
||||||
RestDocumentationResultHandler(String identifier) {
|
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
this.curlRequest = documentCurlRequest(this.identifier, null);
|
this.snippets = Arrays.asList(snippets);
|
||||||
this.httpRequest = documentHttpRequest(this.identifier, null);
|
|
||||||
this.httpResponse = documentHttpResponse(this.identifier, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customizes the default curl request snippet generation to make the given attributes
|
|
||||||
* available.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withCurlRequest(Map<String, Object> attributes) {
|
|
||||||
this.curlRequest = documentCurlRequest(this.identifier, attributes);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customizes the default HTTP request snippet generation to make the given attributes
|
|
||||||
* available.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withHttpRequest(Map<String, Object> attributes) {
|
|
||||||
this.httpRequest = documentHttpRequest(this.identifier, attributes);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customizes the default HTTP response snippet generation to make the given
|
|
||||||
* attributes available.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withHttpResponse(Map<String, Object> attributes) {
|
|
||||||
this.httpResponse = documentHttpResponse(this.identifier, attributes);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the links in the response using the given {@code descriptors}. The links
|
|
||||||
* are extracted from the response based on its content type.
|
|
||||||
* <p>
|
|
||||||
* If a link is present in the response but is not described by one of the descriptors
|
|
||||||
* a failure will occur when this handler is invoked. Similarly, if a link is
|
|
||||||
* described but is not present in the response a failure will also occur when this
|
|
||||||
* handler is invoked.
|
|
||||||
*
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @return {@code this}
|
|
||||||
* @see HypermediaDocumentation#linkWithRel(String)
|
|
||||||
* @see LinkExtractors#extractorForContentType(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withLinks(LinkDescriptor... descriptors) {
|
|
||||||
return withLinks(null, null, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the links in the response using the given {@code descriptors}. The links
|
|
||||||
* are extracted from the response using the given {@code linkExtractor}.
|
|
||||||
* <p>
|
|
||||||
* If a link is present in the response but is not described by one of the descriptors
|
|
||||||
* a failure will occur when this handler is invoked. Similarly, if a link is
|
|
||||||
* described but is not present in the response a failure will also occur when this
|
|
||||||
* handler is invoked.
|
|
||||||
*
|
|
||||||
* @param linkExtractor used to extract the links from the response
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @return {@code this}
|
|
||||||
* @see HypermediaDocumentation#linkWithRel(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withLinks(LinkExtractor linkExtractor,
|
|
||||||
LinkDescriptor... descriptors) {
|
|
||||||
return this.withLinks(null, linkExtractor, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the links in the response using the given {@code descriptors}. The links
|
|
||||||
* are extracted from the response based on its content type. The given
|
|
||||||
* {@code attributes} are made available during the generation of the links snippet.
|
|
||||||
* <p>
|
|
||||||
* If a link is present in the response but is not described by one of the descriptors
|
|
||||||
* a failure will occur when this handler is invoked. Similarly, if a link is
|
|
||||||
* described but is not present in the response a failure will also occur when this
|
|
||||||
* handler is invoked.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @return {@code this}
|
|
||||||
* @see HypermediaDocumentation#linkWithRel(String)
|
|
||||||
* @see LinkExtractors#extractorForContentType(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withLinks(Map<String, Object> attributes,
|
|
||||||
LinkDescriptor... descriptors) {
|
|
||||||
return withLinks(attributes, null, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the links in the response using the given {@code descriptors}. The links
|
|
||||||
* are extracted from the response using the given {@code linkExtractor}. The given
|
|
||||||
* {@code attributes} are made available during the generation of the links snippet.
|
|
||||||
* <p>
|
|
||||||
* If a link is present in the response but is not described by one of the descriptors
|
|
||||||
* a failure will occur when this handler is invoked. Similarly, if a link is
|
|
||||||
* described but is not present in the response a failure will also occur when this
|
|
||||||
* handler is invoked.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @param linkExtractor used to extract the links from the response
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @return {@code this}
|
|
||||||
* @see HypermediaDocumentation#linkWithRel(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withLinks(Map<String, Object> attributes,
|
|
||||||
LinkExtractor linkExtractor, LinkDescriptor... descriptors) {
|
|
||||||
this.delegates.add(documentLinks(this.identifier, attributes, linkExtractor,
|
|
||||||
descriptors));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the fields in the request using the given {@code descriptors}.
|
|
||||||
* <p>
|
|
||||||
* If a field is present in the request but is not documented by one of the
|
|
||||||
* descriptors a failure will occur when this handler is invoked. Similarly, if a
|
|
||||||
* field is documented, is not marked as optional, and is not present in the request a
|
|
||||||
* failure will also occur. For payloads with a hierarchical structure, documenting a
|
|
||||||
* field is sufficient for all of its descendants to also be treated as having been
|
|
||||||
* documented.
|
|
||||||
*
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @return {@code this}
|
|
||||||
* @see PayloadDocumentation#fieldWithPath(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withRequestFields(
|
|
||||||
FieldDescriptor... descriptors) {
|
|
||||||
return this.withRequestFields(null, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the fields in the request using the given {@code descriptors}. The given
|
|
||||||
* {@code attributes} are made available during the generation of the request fields
|
|
||||||
* snippet.
|
|
||||||
* <p>
|
|
||||||
* If a field is present in the request but is not documented by one of the
|
|
||||||
* descriptors a failure will occur when this handler is invoked. Similarly, if a
|
|
||||||
* field is documented, is not marked as optional, and is not present in the request a
|
|
||||||
* failure will also occur. For payloads with a hierarchical structure, documenting a
|
|
||||||
* field is sufficient for all of its descendants to also be treated as having been
|
|
||||||
* documented.
|
|
||||||
*
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @return {@code this}
|
|
||||||
* @see PayloadDocumentation#fieldWithPath(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withRequestFields(
|
|
||||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
|
||||||
this.delegates
|
|
||||||
.add(documentRequestFields(this.identifier, attributes, descriptors));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the fields in the response using the given {@code descriptors}.
|
|
||||||
* <p>
|
|
||||||
* If a field is present in the response but is not documented by one of the
|
|
||||||
* descriptors a failure will occur when this handler is invoked. Similarly, if a
|
|
||||||
* field is documented, is not marked as optional, and is not present in the response
|
|
||||||
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
|
||||||
* a field is sufficient for all of its descendants to also be treated as having been
|
|
||||||
* documented.
|
|
||||||
*
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @return {@code this}
|
|
||||||
* @see PayloadDocumentation#fieldWithPath(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withResponseFields(
|
|
||||||
FieldDescriptor... descriptors) {
|
|
||||||
return this.withResponseFields(null, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document the fields in the response using the given {@code descriptors}. The given
|
|
||||||
* {@code attributes} are made available during the generation of the request fields
|
|
||||||
* snippet.
|
|
||||||
* <p>
|
|
||||||
* If a field is present in the response but is not documented by one of the
|
|
||||||
* descriptors a failure will occur when this handler is invoked. Similarly, if a
|
|
||||||
* field is documented, is not marked as optional, and is not present in the response
|
|
||||||
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
|
||||||
* a field is sufficient for all of its descendants to also be treated as having been
|
|
||||||
* documented.
|
|
||||||
*
|
|
||||||
* @param descriptors the link descriptors
|
|
||||||
* @param attributes the attributes
|
|
||||||
* @return {@code this}
|
|
||||||
* @see PayloadDocumentation#fieldWithPath(String)
|
|
||||||
*/
|
|
||||||
public RestDocumentationResultHandler withResponseFields(
|
|
||||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
|
||||||
this.delegates.add(documentResponseFields(this.identifier, attributes,
|
|
||||||
descriptors));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Documents the parameters in the request's query string using the given
|
|
||||||
* {@code descriptors}.
|
|
||||||
* <p>
|
|
||||||
* If a parameter is present in the query string 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 withQueryParameters(
|
|
||||||
ParameterDescriptor... descriptors) {
|
|
||||||
return this.withQueryParameters(null, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Documents the parameters in the request's query string using the given
|
|
||||||
* {@code descriptors}. The given {@code attributes} are made available during the
|
|
||||||
* generation of the query parameters snippet.
|
|
||||||
* <p>
|
|
||||||
* If a parameter is present in the query string 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 withQueryParameters(
|
|
||||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
|
||||||
this.delegates.add(documentQueryParameters(this.identifier, attributes,
|
|
||||||
descriptors));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Documents the parameters in the request's path using the given {@code descriptors}.
|
|
||||||
* <p>
|
|
||||||
* 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.withPathParameters(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.
|
|
||||||
* <p>
|
|
||||||
* 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<String, Object> attributes, ParameterDescriptor... descriptors) {
|
|
||||||
this.delegates.add(documentPathParameters(this.identifier, attributes,
|
|
||||||
descriptors));
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MvcResult result) throws Exception {
|
public void handle(MvcResult result) throws Exception {
|
||||||
this.curlRequest.handle(result);
|
for (Snippet snippet : getSnippets(result)) {
|
||||||
this.httpRequest.handle(result);
|
snippet.document(this.identifier, result);
|
||||||
this.httpResponse.handle(result);
|
|
||||||
for (ResultHandler delegate : this.delegates) {
|
|
||||||
delegate.handle(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private List<Snippet> getSnippets(MvcResult result) {
|
||||||
|
List<Snippet> combinedSnippets = new ArrayList<>((List<Snippet>) result.getRequest()
|
||||||
|
.getAttribute("org.springframework.restdocs.defaultSnippets"));
|
||||||
|
combinedSnippets.addAll(this.snippets);
|
||||||
|
return combinedSnippets;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
|
|||||||
abstract class AbstractConfigurer {
|
abstract class AbstractConfigurer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the configuration, possibly be modifying the given {@code request}
|
* Applies the configuration, possibly by modifying the given {@code request}
|
||||||
* @param request the request that may be modified
|
* @param request the request that may be modified
|
||||||
*/
|
*/
|
||||||
abstract void apply(MockHttpServletRequest request);
|
abstract void apply(MockHttpServletRequest request);
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
|||||||
|
|
||||||
private final RequestPostProcessor requestPostProcessor;
|
private final RequestPostProcessor requestPostProcessor;
|
||||||
|
|
||||||
private TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer();
|
private final TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer();
|
||||||
|
|
||||||
private WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer();
|
private final WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link RestDocumentationConfigurer}.
|
* Creates a new {@link RestDocumentationConfigurer}.
|
||||||
@@ -65,19 +65,44 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
|||||||
this.templateEngineConfigurer));
|
this.templateEngineConfigurer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link UriConfigurer} that can be used to configure the request URIs that
|
||||||
|
* will be documented.
|
||||||
|
*
|
||||||
|
* @return the URI configurer
|
||||||
|
*/
|
||||||
public UriConfigurer uris() {
|
public UriConfigurer uris() {
|
||||||
return this.uriConfigurer;
|
return this.uriConfigurer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link SnippetConfigurer} that can be used to configure the snippets that
|
||||||
|
* will be generated.
|
||||||
|
*
|
||||||
|
* @return the snippet configurer
|
||||||
|
*/
|
||||||
public SnippetConfigurer snippets() {
|
public SnippetConfigurer snippets() {
|
||||||
return this.snippetConfigurer;
|
return this.snippetConfigurer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the {@link TemplateEngine} that will be used for snippet rendering.
|
||||||
|
*
|
||||||
|
* @param templateEngine the template engine to use
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
public RestDocumentationConfigurer templateEngine(TemplateEngine templateEngine) {
|
public RestDocumentationConfigurer templateEngine(TemplateEngine templateEngine) {
|
||||||
this.templateEngineConfigurer.setTemplateEngine(templateEngine);
|
this.templateEngineConfigurer.setTemplateEngine(templateEngine);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the {@link WriterResolver} that will be used to resolve a writer for a
|
||||||
|
* snippet.
|
||||||
|
*
|
||||||
|
* @param writerResolver The writer resolver to use
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
public RestDocumentationConfigurer writerResolver(WriterResolver writerResolver) {
|
public RestDocumentationConfigurer writerResolver(WriterResolver writerResolver) {
|
||||||
this.writerResolverConfigurer.setWriterResolver(writerResolver);
|
this.writerResolverConfigurer.setWriterResolver(writerResolver);
|
||||||
return this;
|
return this;
|
||||||
@@ -175,4 +200,5 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,10 +33,12 @@ public final class RestDocumentationContext {
|
|||||||
|
|
||||||
private final TestContext testContext;
|
private final TestContext testContext;
|
||||||
|
|
||||||
public RestDocumentationContext() {
|
/**
|
||||||
this(null);
|
* Creates a new {@code RestDocumentationContext} backed by the given
|
||||||
}
|
* {@code testContext}.
|
||||||
|
*
|
||||||
|
* @param testContext the test context
|
||||||
|
*/
|
||||||
public RestDocumentationContext(TestContext testContext) {
|
public RestDocumentationContext(TestContext testContext) {
|
||||||
this.testContext = testContext;
|
this.testContext = testContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ public class RestDocumentationContextPlaceholderResolver implements PlaceholderR
|
|||||||
|
|
||||||
private final RestDocumentationContext context;
|
private final RestDocumentationContext context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new placeholder resolver that will resolve placeholders using the given
|
||||||
|
* {@code context}.
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
*/
|
||||||
public RestDocumentationContextPlaceholderResolver(RestDocumentationContext context) {
|
public RestDocumentationContextPlaceholderResolver(RestDocumentationContext context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,13 @@
|
|||||||
|
|
||||||
package org.springframework.restdocs.config;
|
package org.springframework.restdocs.config;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.restdocs.curl.CurlDocumentation;
|
||||||
|
import org.springframework.restdocs.http.HttpDocumentation;
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.snippet.WriterResolver;
|
import org.springframework.restdocs.snippet.WriterResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +34,10 @@ import org.springframework.restdocs.snippet.WriterResolver;
|
|||||||
public class SnippetConfigurer extends
|
public class SnippetConfigurer extends
|
||||||
AbstractNestedConfigurer<RestDocumentationConfigurer> {
|
AbstractNestedConfigurer<RestDocumentationConfigurer> {
|
||||||
|
|
||||||
|
private List<Snippet> defaultSnippets = Arrays.asList(
|
||||||
|
CurlDocumentation.curlRequest(), HttpDocumentation.httpRequest(),
|
||||||
|
HttpDocumentation.httpResponse());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default encoding for documentation snippets
|
* The default encoding for documentation snippets
|
||||||
* @see #withEncoding(String)
|
* @see #withEncoding(String)
|
||||||
@@ -43,7 +53,7 @@ public class SnippetConfigurer extends
|
|||||||
/**
|
/**
|
||||||
* Configures any documentation snippets to be written using the given
|
* Configures any documentation snippets to be written using the given
|
||||||
* {@code encoding}. The default is UTF-8.
|
* {@code encoding}. The default is UTF-8.
|
||||||
* @param encoding The encoding
|
* @param encoding the encoding
|
||||||
* @return {@code this}
|
* @return {@code this}
|
||||||
*/
|
*/
|
||||||
public SnippetConfigurer withEncoding(String encoding) {
|
public SnippetConfigurer withEncoding(String encoding) {
|
||||||
@@ -55,5 +65,18 @@ public class SnippetConfigurer extends
|
|||||||
void apply(MockHttpServletRequest request) {
|
void apply(MockHttpServletRequest request) {
|
||||||
((WriterResolver) request.getAttribute(WriterResolver.class.getName()))
|
((WriterResolver) request.getAttribute(WriterResolver.class.getName()))
|
||||||
.setEncoding(this.snippetEncoding);
|
.setEncoding(this.snippetEncoding);
|
||||||
|
request.setAttribute("org.springframework.restdocs.defaultSnippets",
|
||||||
|
this.defaultSnippets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the documentation snippets that will be produced by default.
|
||||||
|
*
|
||||||
|
* @param defaultSnippets the default snippets
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public SnippetConfigurer withDefaults(Snippet... defaultSnippets) {
|
||||||
|
this.defaultSnippets = Arrays.asList(defaultSnippets);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,19 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.restdocs.curl;
|
package org.springframework.restdocs.curl;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.DocumentableHttpServletRequest;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory methods for documenting a RESTful API as if it were being driven using
|
* Static factory methods for documenting a RESTful API as if it were being driven using
|
||||||
@@ -46,151 +36,25 @@ public abstract class CurlDocumentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produces a documentation snippet containing the request formatted as a cURL command
|
* Returns a handler that will produce a snippet containing the curl request for the
|
||||||
|
* API call.
|
||||||
|
*
|
||||||
|
* @return the handler that will produce the snippet
|
||||||
|
*/
|
||||||
|
public static Snippet curlRequest() {
|
||||||
|
return new CurlRequestSnippet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet containing the curl request for the
|
||||||
|
* API call. The given {@code attributes} will be available during snippet generation.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
|
||||||
* @param attributes Attributes made available during rendering of the curl request
|
* @param attributes Attributes made available during rendering of the curl request
|
||||||
* snippet
|
* snippet
|
||||||
* @return the handler that will produce the snippet
|
* @return the handler that will produce the snippet
|
||||||
*/
|
*/
|
||||||
public static SnippetWritingResultHandler documentCurlRequest(String identifier,
|
public static Snippet curlRequest(Map<String, Object> attributes) {
|
||||||
Map<String, Object> attributes) {
|
return new CurlRequestSnippet(attributes);
|
||||||
return new CurlRequestWritingResultHandler(identifier, attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class CurlRequestWritingResultHandler extends
|
|
||||||
SnippetWritingResultHandler {
|
|
||||||
|
|
||||||
private static final String SCHEME_HTTP = "http";
|
|
||||||
|
|
||||||
private static final String SCHEME_HTTPS = "https";
|
|
||||||
|
|
||||||
private static final int STANDARD_PORT_HTTP = 80;
|
|
||||||
|
|
||||||
private static final int STANDARD_PORT_HTTPS = 443;
|
|
||||||
|
|
||||||
private CurlRequestWritingResultHandler(String identifier,
|
|
||||||
Map<String, Object> attributes) {
|
|
||||||
super(identifier, "curl-request", attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> doHandle(MvcResult result) throws IOException {
|
|
||||||
Map<String, Object> model = new HashMap<String, Object>();
|
|
||||||
model.put("arguments", getCurlCommandArguments(result));
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCurlCommandArguments(MvcResult result) throws IOException {
|
|
||||||
StringWriter command = new StringWriter();
|
|
||||||
PrintWriter printer = new PrintWriter(command);
|
|
||||||
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
|
||||||
result.getRequest());
|
|
||||||
|
|
||||||
printer.print("'");
|
|
||||||
writeAuthority(request, printer);
|
|
||||||
writePathAndQueryString(request, printer);
|
|
||||||
printer.print("'");
|
|
||||||
|
|
||||||
writeOptionToIncludeHeadersInOutput(printer);
|
|
||||||
writeHttpMethodIfNecessary(request, printer);
|
|
||||||
writeHeaders(request, printer);
|
|
||||||
|
|
||||||
if (request.isMultipartRequest()) {
|
|
||||||
writeParts(request, printer);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeContent(request, printer);
|
|
||||||
|
|
||||||
return command.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeAuthority(DocumentableHttpServletRequest request,
|
|
||||||
PrintWriter writer) {
|
|
||||||
writer.print(String.format("%s://%s", request.getScheme(), request.getHost()));
|
|
||||||
|
|
||||||
if (isNonStandardPort(request)) {
|
|
||||||
writer.print(String.format(":%d", request.getPort()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isNonStandardPort(DocumentableHttpServletRequest request) {
|
|
||||||
return (SCHEME_HTTP.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTP)
|
|
||||||
|| (SCHEME_HTTPS.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTPS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writePathAndQueryString(DocumentableHttpServletRequest request,
|
|
||||||
PrintWriter writer) {
|
|
||||||
if (StringUtils.hasText(request.getContextPath())) {
|
|
||||||
writer.print(String.format(
|
|
||||||
request.getContextPath().startsWith("/") ? "%s" : "/%s",
|
|
||||||
request.getContextPath()));
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.print(request.getRequestUriWithQueryString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) {
|
|
||||||
writer.print(" -i");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request,
|
|
||||||
PrintWriter writer) {
|
|
||||||
if (!request.isGetRequest()) {
|
|
||||||
writer.print(String.format(" -X %s", request.getMethod()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeHeaders(DocumentableHttpServletRequest request,
|
|
||||||
PrintWriter writer) {
|
|
||||||
for (Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
|
|
||||||
for (String header : entry.getValue()) {
|
|
||||||
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer)
|
|
||||||
throws IOException {
|
|
||||||
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
|
||||||
.entrySet()) {
|
|
||||||
for (MultipartFile file : entry.getValue()) {
|
|
||||||
writer.printf(" -F '%s=", file.getName());
|
|
||||||
if (!StringUtils.hasText(file.getOriginalFilename())) {
|
|
||||||
writer.append(new String(file.getBytes()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
writer.printf("@%s", file.getOriginalFilename());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.hasText(file.getContentType())) {
|
|
||||||
writer.append(";type=").append(file.getContentType());
|
|
||||||
}
|
|
||||||
writer.append("'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeContent(DocumentableHttpServletRequest request,
|
|
||||||
PrintWriter writer) throws IOException {
|
|
||||||
if (request.getContentLength() > 0) {
|
|
||||||
writer.print(String.format(" -d '%s'", request.getContentAsString()));
|
|
||||||
}
|
|
||||||
else if (request.isMultipartRequest()) {
|
|
||||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
|
||||||
for (String value : entry.getValue()) {
|
|
||||||
writer.print(String.format(" -F '%s=%s'", entry.getKey(), value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (request.isPostRequest() || request.isPutRequest()) {
|
|
||||||
String queryString = request.getParameterMapAsQueryString();
|
|
||||||
if (StringUtils.hasText(queryString)) {
|
|
||||||
writer.print(String.format(" -d '%s'", queryString));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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.curl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.springframework.restdocs.snippet.DocumentableHttpServletRequest;
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Snippet} that documents the curl command for a request.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class CurlRequestSnippet extends TemplatedSnippet {
|
||||||
|
|
||||||
|
private static final String SCHEME_HTTP = "http";
|
||||||
|
|
||||||
|
private static final String SCHEME_HTTPS = "https";
|
||||||
|
|
||||||
|
private static final int STANDARD_PORT_HTTP = 80;
|
||||||
|
|
||||||
|
private static final int STANDARD_PORT_HTTPS = 443;
|
||||||
|
|
||||||
|
CurlRequestSnippet() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlRequestSnippet(Map<String, Object> attributes) {
|
||||||
|
super("curl-request", attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> document(MvcResult result) throws IOException {
|
||||||
|
Map<String, Object> model = new HashMap<String, Object>();
|
||||||
|
model.put("arguments", getCurlCommandArguments(result));
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCurlCommandArguments(MvcResult result) throws IOException {
|
||||||
|
StringWriter command = new StringWriter();
|
||||||
|
PrintWriter printer = new PrintWriter(command);
|
||||||
|
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
||||||
|
result.getRequest());
|
||||||
|
|
||||||
|
printer.print("'");
|
||||||
|
writeAuthority(request, printer);
|
||||||
|
writePathAndQueryString(request, printer);
|
||||||
|
printer.print("'");
|
||||||
|
|
||||||
|
writeOptionToIncludeHeadersInOutput(printer);
|
||||||
|
writeHttpMethodIfNecessary(request, printer);
|
||||||
|
writeHeaders(request, printer);
|
||||||
|
|
||||||
|
if (request.isMultipartRequest()) {
|
||||||
|
writeParts(request, printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeContent(request, printer);
|
||||||
|
|
||||||
|
return command.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeAuthority(DocumentableHttpServletRequest request, PrintWriter writer) {
|
||||||
|
writer.print(String.format("%s://%s", request.getScheme(), request.getHost()));
|
||||||
|
|
||||||
|
if (isNonStandardPort(request)) {
|
||||||
|
writer.print(String.format(":%d", request.getPort()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNonStandardPort(DocumentableHttpServletRequest request) {
|
||||||
|
return (SCHEME_HTTP.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTP)
|
||||||
|
|| (SCHEME_HTTPS.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePathAndQueryString(DocumentableHttpServletRequest request,
|
||||||
|
PrintWriter writer) {
|
||||||
|
if (StringUtils.hasText(request.getContextPath())) {
|
||||||
|
writer.print(String.format(request.getContextPath().startsWith("/") ? "%s"
|
||||||
|
: "/%s", request.getContextPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.print(request.getRequestUriWithQueryString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) {
|
||||||
|
writer.print(" -i");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request,
|
||||||
|
PrintWriter writer) {
|
||||||
|
if (!request.isGetRequest()) {
|
||||||
|
writer.print(String.format(" -X %s", request.getMethod()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHeaders(DocumentableHttpServletRequest request, PrintWriter writer) {
|
||||||
|
for (Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
|
||||||
|
for (String header : entry.getValue()) {
|
||||||
|
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer)
|
||||||
|
throws IOException {
|
||||||
|
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
||||||
|
.entrySet()) {
|
||||||
|
for (MultipartFile file : entry.getValue()) {
|
||||||
|
writer.printf(" -F '%s=", file.getName());
|
||||||
|
if (!StringUtils.hasText(file.getOriginalFilename())) {
|
||||||
|
writer.append(new String(file.getBytes()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writer.printf("@%s", file.getOriginalFilename());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasText(file.getContentType())) {
|
||||||
|
writer.append(";type=").append(file.getContentType());
|
||||||
|
}
|
||||||
|
writer.append("'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeContent(DocumentableHttpServletRequest request, PrintWriter writer)
|
||||||
|
throws IOException {
|
||||||
|
if (request.getContentLength() > 0) {
|
||||||
|
writer.print(String.format(" -d '%s'", request.getContentAsString()));
|
||||||
|
}
|
||||||
|
else if (request.isMultipartRequest()) {
|
||||||
|
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
||||||
|
for (String value : entry.getValue()) {
|
||||||
|
writer.print(String.format(" -F '%s=%s'", entry.getKey(), value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request.isPostRequest() || request.isPutRequest()) {
|
||||||
|
String queryString = request.getParameterMapAsQueryString();
|
||||||
|
if (StringUtils.hasText(queryString)) {
|
||||||
|
writer.print(String.format(" -d '%s'", queryString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,23 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.restdocs.http;
|
package org.springframework.restdocs.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.restdocs.snippet.DocumentableHttpServletRequest;
|
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory methods for documenting a RESTful API's HTTP requests.
|
* Static factory methods for documenting a RESTful API's HTTP requests.
|
||||||
@@ -42,213 +28,49 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
*/
|
*/
|
||||||
public abstract class HttpDocumentation {
|
public abstract class HttpDocumentation {
|
||||||
|
|
||||||
private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
|
||||||
|
|
||||||
private HttpDocumentation() {
|
private HttpDocumentation() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produces a documentation snippet containing the request formatted as an HTTP
|
* Returns a handler that will produce a snippet containing the HTTP request for the
|
||||||
* request
|
* API call.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
|
||||||
* @param attributes Attributes made available during rendering of the HTTP requst
|
|
||||||
* snippet
|
|
||||||
* @return the handler that will produce the snippet
|
* @return the handler that will produce the snippet
|
||||||
*/
|
*/
|
||||||
public static SnippetWritingResultHandler documentHttpRequest(String identifier,
|
public static Snippet httpRequest() {
|
||||||
Map<String, Object> attributes) {
|
return new HttpRequestSnippet();
|
||||||
return new HttpRequestWritingResultHandler(identifier, attributes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produces a documentation snippet containing the response formatted as the HTTP
|
* Returns a handler that will produce a snippet containing the HTTP request for the
|
||||||
* response sent by the server
|
* API call. The given {@code attributes} will be available during snippet generation.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
* @param attributes the attributes
|
||||||
* @param attributes Attributes made available during rendering of the HTTP response
|
|
||||||
* snippet
|
|
||||||
* @return the handler that will produce the snippet
|
* @return the handler that will produce the snippet
|
||||||
*/
|
*/
|
||||||
public static SnippetWritingResultHandler documentHttpResponse(String identifier,
|
public static Snippet httpRequest(Map<String, Object> attributes) {
|
||||||
Map<String, Object> attributes) {
|
return new HttpRequestSnippet(attributes);
|
||||||
return new HttpResponseWritingResultHandler(identifier, attributes);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class HttpRequestWritingResultHandler extends
|
/**
|
||||||
SnippetWritingResultHandler {
|
* Returns a handler that will produce a snippet containing the HTTP response for the
|
||||||
|
* API call.
|
||||||
private HttpRequestWritingResultHandler(String identifier,
|
* @return the handler that will produce the snippet
|
||||||
Map<String, Object> attributes) {
|
*/
|
||||||
super(identifier, "http-request", attributes);
|
public static Snippet httpResponse() {
|
||||||
}
|
return new HttpResponseSnippet();
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> doHandle(MvcResult result) throws IOException {
|
|
||||||
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
|
||||||
result.getRequest());
|
|
||||||
Map<String, Object> model = new HashMap<String, Object>();
|
|
||||||
model.put("method", result.getRequest().getMethod());
|
|
||||||
model.put("path", request.getRequestUriWithQueryString());
|
|
||||||
model.put("headers", getHeaders(request));
|
|
||||||
model.put("requestBody", getRequestBody(request));
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, String>> getHeaders(
|
|
||||||
DocumentableHttpServletRequest request) {
|
|
||||||
List<Map<String, String>> headers = new ArrayList<>();
|
|
||||||
if (requiresHostHeader(request)) {
|
|
||||||
headers.add(header(HttpHeaders.HOST, request.getHost()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Entry<String, List<String>> header : request.getHeaders().entrySet()) {
|
|
||||||
for (String value : header.getValue()) {
|
|
||||||
if (header.getKey() == HttpHeaders.CONTENT_TYPE
|
|
||||||
&& request.isMultipartRequest()) {
|
|
||||||
headers.add(header(header.getKey(), String.format(
|
|
||||||
"%s; boundary=%s", value, MULTIPART_BOUNDARY)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
headers.add(header(header.getKey(), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (requiresFormEncodingContentType(request)) {
|
|
||||||
headers.add(header(HttpHeaders.CONTENT_TYPE,
|
|
||||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE));
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getRequestBody(DocumentableHttpServletRequest request)
|
|
||||||
throws IOException {
|
|
||||||
StringWriter httpRequest = new StringWriter();
|
|
||||||
PrintWriter writer = new PrintWriter(httpRequest);
|
|
||||||
if (request.getContentLength() > 0) {
|
|
||||||
writer.println();
|
|
||||||
writer.print(request.getContentAsString());
|
|
||||||
}
|
|
||||||
else if (request.isPostRequest() || request.isPutRequest()) {
|
|
||||||
if (request.isMultipartRequest()) {
|
|
||||||
writeParts(request, writer);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
String queryString = request.getParameterMapAsQueryString();
|
|
||||||
if (StringUtils.hasText(queryString)) {
|
|
||||||
writer.println();
|
|
||||||
writer.print(queryString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return httpRequest.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer)
|
|
||||||
throws IOException {
|
|
||||||
writer.println();
|
|
||||||
for (Entry<String, String[]> parameter : request.getParameterMap().entrySet()) {
|
|
||||||
for (String value : parameter.getValue()) {
|
|
||||||
writePartBoundary(writer);
|
|
||||||
writePart(parameter.getKey(), value, null, writer);
|
|
||||||
writer.println();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
|
||||||
.entrySet()) {
|
|
||||||
for (MultipartFile file : entry.getValue()) {
|
|
||||||
writePartBoundary(writer);
|
|
||||||
writePart(file, writer);
|
|
||||||
writer.println();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeMultipartEnd(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writePartBoundary(PrintWriter writer) {
|
|
||||||
writer.printf("--%s%n", MULTIPART_BOUNDARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writePart(String name, String value, String contentType,
|
|
||||||
PrintWriter writer) {
|
|
||||||
writer.printf("Content-Disposition: form-data; name=%s%n", name);
|
|
||||||
if (StringUtils.hasText(contentType)) {
|
|
||||||
writer.printf("Content-Type: %s%n", contentType);
|
|
||||||
}
|
|
||||||
writer.println();
|
|
||||||
writer.print(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writePart(MultipartFile part, PrintWriter writer) throws IOException {
|
|
||||||
writePart(part.getName(), new String(part.getBytes()), part.getContentType(),
|
|
||||||
writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeMultipartEnd(PrintWriter writer) {
|
|
||||||
writer.printf("--%s--", MULTIPART_BOUNDARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean requiresHostHeader(DocumentableHttpServletRequest request) {
|
|
||||||
return request.getHeaders().get(HttpHeaders.HOST) == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean requiresFormEncodingContentType(
|
|
||||||
DocumentableHttpServletRequest request) {
|
|
||||||
return request.getHeaders().getContentType() == null
|
|
||||||
&& (request.isPostRequest() || request.isPutRequest())
|
|
||||||
&& StringUtils.hasText(request.getParameterMapAsQueryString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> header(String name, String value) {
|
|
||||||
Map<String, String> header = new HashMap<>();
|
|
||||||
header.put("name", name);
|
|
||||||
header.put("value", value);
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class HttpResponseWritingResultHandler extends
|
/**
|
||||||
SnippetWritingResultHandler {
|
* Returns a handler that will produce a snippet containing the HTTP response for the
|
||||||
|
* API call. The given {@code attributes} will be available during snippet generation.
|
||||||
private HttpResponseWritingResultHandler(String identifier,
|
*
|
||||||
Map<String, Object> attributes) {
|
* @param attributes the attributes
|
||||||
super(identifier, "http-response", attributes);
|
* @return the handler that will produce the snippet
|
||||||
}
|
*/
|
||||||
|
public static Snippet httpResponse(Map<String, Object> attributes) {
|
||||||
@Override
|
return new HttpResponseSnippet(attributes);
|
||||||
public Map<String, Object> doHandle(MvcResult result) throws IOException {
|
|
||||||
HttpStatus status = HttpStatus.valueOf(result.getResponse().getStatus());
|
|
||||||
Map<String, Object> model = new HashMap<String, Object>();
|
|
||||||
model.put(
|
|
||||||
"responseBody",
|
|
||||||
StringUtils.hasLength(result.getResponse().getContentAsString()) ? String
|
|
||||||
.format("%n%s", result.getResponse().getContentAsString())
|
|
||||||
: "");
|
|
||||||
model.put("statusCode", status.value());
|
|
||||||
model.put("statusReason", status.getReasonPhrase());
|
|
||||||
model.put("headers", headers(result));
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, String>> headers(MvcResult result) {
|
|
||||||
List<Map<String, String>> headers = new ArrayList<>();
|
|
||||||
for (String headerName : result.getResponse().getHeaderNames()) {
|
|
||||||
for (String header : result.getResponse().getHeaders(headerName)) {
|
|
||||||
headers.add(header(headerName, header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> header(String name, String value) {
|
|
||||||
Map<String, String> header = new HashMap<>();
|
|
||||||
header.put("name", name);
|
|
||||||
header.put("value", value);
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* 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.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.restdocs.snippet.DocumentableHttpServletRequest;
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Snippet} that documents an HTTP request.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class HttpRequestSnippet extends TemplatedSnippet {
|
||||||
|
|
||||||
|
private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||||
|
|
||||||
|
HttpRequestSnippet() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequestSnippet(Map<String, Object> attributes) {
|
||||||
|
super("http-request", attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> document(MvcResult result) throws IOException {
|
||||||
|
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
||||||
|
result.getRequest());
|
||||||
|
Map<String, Object> model = new HashMap<String, Object>();
|
||||||
|
model.put("method", result.getRequest().getMethod());
|
||||||
|
model.put("path", request.getRequestUriWithQueryString());
|
||||||
|
model.put("headers", getHeaders(request));
|
||||||
|
model.put("requestBody", getRequestBody(request));
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, String>> getHeaders(DocumentableHttpServletRequest request) {
|
||||||
|
List<Map<String, String>> headers = new ArrayList<>();
|
||||||
|
if (requiresHostHeader(request)) {
|
||||||
|
headers.add(header(HttpHeaders.HOST, request.getHost()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Entry<String, List<String>> header : request.getHeaders().entrySet()) {
|
||||||
|
for (String value : header.getValue()) {
|
||||||
|
if (header.getKey() == HttpHeaders.CONTENT_TYPE
|
||||||
|
&& request.isMultipartRequest()) {
|
||||||
|
headers.add(header(header.getKey(),
|
||||||
|
String.format("%s; boundary=%s", value, MULTIPART_BOUNDARY)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
headers.add(header(header.getKey(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (requiresFormEncodingContentType(request)) {
|
||||||
|
headers.add(header(HttpHeaders.CONTENT_TYPE,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED_VALUE));
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequestBody(DocumentableHttpServletRequest request)
|
||||||
|
throws IOException {
|
||||||
|
StringWriter httpRequest = new StringWriter();
|
||||||
|
PrintWriter writer = new PrintWriter(httpRequest);
|
||||||
|
if (request.getContentLength() > 0) {
|
||||||
|
writer.println();
|
||||||
|
writer.print(request.getContentAsString());
|
||||||
|
}
|
||||||
|
else if (request.isPostRequest() || request.isPutRequest()) {
|
||||||
|
if (request.isMultipartRequest()) {
|
||||||
|
writeParts(request, writer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String queryString = request.getParameterMapAsQueryString();
|
||||||
|
if (StringUtils.hasText(queryString)) {
|
||||||
|
writer.println();
|
||||||
|
writer.print(queryString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return httpRequest.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer)
|
||||||
|
throws IOException {
|
||||||
|
writer.println();
|
||||||
|
for (Entry<String, String[]> parameter : request.getParameterMap().entrySet()) {
|
||||||
|
for (String value : parameter.getValue()) {
|
||||||
|
writePartBoundary(writer);
|
||||||
|
writePart(parameter.getKey(), value, null, writer);
|
||||||
|
writer.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
||||||
|
.entrySet()) {
|
||||||
|
for (MultipartFile file : entry.getValue()) {
|
||||||
|
writePartBoundary(writer);
|
||||||
|
writePart(file, writer);
|
||||||
|
writer.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeMultipartEnd(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePartBoundary(PrintWriter writer) {
|
||||||
|
writer.printf("--%s%n", MULTIPART_BOUNDARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePart(String name, String value, String contentType,
|
||||||
|
PrintWriter writer) {
|
||||||
|
writer.printf("Content-Disposition: form-data; name=%s%n", name);
|
||||||
|
if (StringUtils.hasText(contentType)) {
|
||||||
|
writer.printf("Content-Type: %s%n", contentType);
|
||||||
|
}
|
||||||
|
writer.println();
|
||||||
|
writer.print(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePart(MultipartFile part, PrintWriter writer) throws IOException {
|
||||||
|
writePart(part.getName(), new String(part.getBytes()), part.getContentType(),
|
||||||
|
writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMultipartEnd(PrintWriter writer) {
|
||||||
|
writer.printf("--%s--", MULTIPART_BOUNDARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean requiresHostHeader(DocumentableHttpServletRequest request) {
|
||||||
|
return request.getHeaders().get(HttpHeaders.HOST) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean requiresFormEncodingContentType(DocumentableHttpServletRequest request) {
|
||||||
|
return request.getHeaders().getContentType() == null
|
||||||
|
&& (request.isPostRequest() || request.isPutRequest())
|
||||||
|
&& StringUtils.hasText(request.getParameterMapAsQueryString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> header(String name, String value) {
|
||||||
|
Map<String, String> header = new HashMap<>();
|
||||||
|
header.put("name", name);
|
||||||
|
header.put("value", value);
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Snippet} that documents an HTTP response.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class HttpResponseSnippet extends TemplatedSnippet {
|
||||||
|
|
||||||
|
HttpResponseSnippet() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponseSnippet(Map<String, Object> attributes) {
|
||||||
|
super("http-response", attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> document(MvcResult result) throws IOException {
|
||||||
|
HttpStatus status = HttpStatus.valueOf(result.getResponse().getStatus());
|
||||||
|
Map<String, Object> model = new HashMap<String, Object>();
|
||||||
|
model.put(
|
||||||
|
"responseBody",
|
||||||
|
StringUtils.hasLength(result.getResponse().getContentAsString()) ? String
|
||||||
|
.format("%n%s", result.getResponse().getContentAsString()) : "");
|
||||||
|
model.put("statusCode", status.value());
|
||||||
|
model.put("statusReason", status.getReasonPhrase());
|
||||||
|
model.put("headers", headers(result));
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, String>> headers(MvcResult result) {
|
||||||
|
List<Map<String, String>> headers = new ArrayList<>();
|
||||||
|
for (String headerName : result.getResponse().getHeaderNames()) {
|
||||||
|
for (String header : result.getResponse().getHeaders(headerName)) {
|
||||||
|
headers.add(header(headerName, header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> header(String name, String value) {
|
||||||
|
Map<String, String> header = new HashMap<>();
|
||||||
|
header.put("name", name);
|
||||||
|
header.put("value", value);
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.hypermedia;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for a {@link LinkExtractor} that extracts links from JSON
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
abstract class AbstractJsonLinkExtractor implements LinkExtractor {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
||||||
|
throws IOException {
|
||||||
|
Map<String, Object> jsonContent = this.objectMapper.readValue(
|
||||||
|
response.getContentAsString(), Map.class);
|
||||||
|
return extractLinks(jsonContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Map<String, List<Link>> extractLinks(Map<String, Object> json);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.hypermedia;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link LinkExtractor} that extracts links in Atom format.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
class AtomLinkExtractor extends AbstractJsonLinkExtractor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
||||||
|
MultiValueMap<String, Link> extractedLinks = new LinkedMultiValueMap<>();
|
||||||
|
Object possibleLinks = json.get("links");
|
||||||
|
if (possibleLinks instanceof Collection) {
|
||||||
|
Collection<Object> linksCollection = (Collection<Object>) possibleLinks;
|
||||||
|
for (Object linkObject : linksCollection) {
|
||||||
|
if (linkObject instanceof Map) {
|
||||||
|
Link link = maybeCreateLink((Map<String, Object>) linkObject);
|
||||||
|
maybeStoreLink(link, extractedLinks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extractedLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Link maybeCreateLink(Map<String, Object> linkMap) {
|
||||||
|
Object hrefObject = linkMap.get("href");
|
||||||
|
Object relObject = linkMap.get("rel");
|
||||||
|
if (relObject instanceof String && hrefObject instanceof String) {
|
||||||
|
return new Link((String) relObject, (String) hrefObject);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void maybeStoreLink(Link link,
|
||||||
|
MultiValueMap<String, Link> extractedLinks) {
|
||||||
|
if (link != null) {
|
||||||
|
extractedLinks.add(link.getRel(), link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package org.springframework.restdocs.hypermedia;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link LinkExtractor} that delegates to other link extractors based on the response's
|
||||||
|
* content type.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ContentTypeLinkExtractor implements LinkExtractor {
|
||||||
|
|
||||||
|
private Map<MediaType, LinkExtractor> linkExtractors = new HashMap<>();
|
||||||
|
|
||||||
|
ContentTypeLinkExtractor() {
|
||||||
|
this.linkExtractors.put(MediaType.APPLICATION_JSON, new AtomLinkExtractor());
|
||||||
|
this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, new HalLinkExtractor());
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentTypeLinkExtractor(Map<MediaType, LinkExtractor> linkExtractors) {
|
||||||
|
this.linkExtractors.putAll(linkExtractors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
||||||
|
throws IOException {
|
||||||
|
String contentType = response.getContentType();
|
||||||
|
LinkExtractor extractorForContentType = getExtractorForContentType(contentType);
|
||||||
|
if (extractorForContentType != null) {
|
||||||
|
return extractorForContentType.extractLinks(response);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"No LinkExtractor has been provided and one is not available for the "
|
||||||
|
+ "content type " + contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkExtractor getExtractorForContentType(String contentType) {
|
||||||
|
if (StringUtils.hasText(contentType)) {
|
||||||
|
MediaType mediaType = MediaType.parseMediaType(contentType);
|
||||||
|
for (Entry<MediaType, LinkExtractor> entry : this.linkExtractors.entrySet()) {
|
||||||
|
if (mediaType.isCompatibleWith(entry.getKey())) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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.hypermedia;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link LinkExtractor} that extracts links in Hypermedia Application Language (HAL)
|
||||||
|
* format.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class HalLinkExtractor extends AbstractJsonLinkExtractor {
|
||||||
|
|
||||||
|
static final MediaType HAL_MEDIA_TYPE = new MediaType("application", "hal+json");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
||||||
|
Map<String, List<Link>> extractedLinks = new LinkedHashMap<>();
|
||||||
|
Object possibleLinks = json.get("_links");
|
||||||
|
if (possibleLinks instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> links = (Map<String, Object>) possibleLinks;
|
||||||
|
for (Entry<String, Object> entry : links.entrySet()) {
|
||||||
|
String rel = entry.getKey();
|
||||||
|
extractedLinks.put(rel, convertToLinks(entry.getValue(), rel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extractedLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Link> convertToLinks(Object object, String rel) {
|
||||||
|
List<Link> links = new ArrayList<>();
|
||||||
|
if (object instanceof Collection) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Collection<Object> hrefObjects = (Collection<Object>) object;
|
||||||
|
for (Object hrefObject : hrefObjects) {
|
||||||
|
maybeAddLink(maybeCreateLink(rel, hrefObject), links);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
maybeAddLink(maybeCreateLink(rel, object), links);
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Link maybeCreateLink(String rel, Object possibleHref) {
|
||||||
|
if (possibleHref instanceof String) {
|
||||||
|
return new Link(rel, (String) possibleHref);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void maybeAddLink(Link possibleLink, List<Link> links) {
|
||||||
|
if (possibleLink != null) {
|
||||||
|
links.add(possibleLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,10 +19,10 @@ package org.springframework.restdocs.hypermedia;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.restdocs.RestDocumentationResultHandler;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory methods for documenting a RESTful API that utilises Hypermedia.
|
* Static factory methods for documenting a RESTful API that utilizes Hypermedia.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
@@ -37,29 +37,89 @@ public abstract class HypermediaDocumentation {
|
|||||||
*
|
*
|
||||||
* @param rel The rel of the link
|
* @param rel The rel of the link
|
||||||
* @return a {@code LinkDescriptor} ready for further configuration
|
* @return a {@code LinkDescriptor} ready for further configuration
|
||||||
* @see RestDocumentationResultHandler#withLinks(LinkDescriptor...)
|
|
||||||
* @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...)
|
|
||||||
*/
|
*/
|
||||||
public static LinkDescriptor linkWithRel(String rel) {
|
public static LinkDescriptor linkWithRel(String rel) {
|
||||||
return new LinkDescriptor(rel);
|
return new LinkDescriptor(rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code LinkSnippetResultHandler} that will produce a documentation
|
* Returns a handler that will produce a snippet documenting the links in the API
|
||||||
* snippet for a response's links.
|
* call's response. Links will be extracted from the response automatically based on
|
||||||
|
* its content type.
|
||||||
|
*
|
||||||
|
* @param descriptors The descriptions of the response's links
|
||||||
|
* @return the handler
|
||||||
|
*/
|
||||||
|
public static Snippet links(LinkDescriptor... descriptors) {
|
||||||
|
return new LinksSnippet(new ContentTypeLinkExtractor(),
|
||||||
|
Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the links in the API
|
||||||
|
* call's response. The given {@code attributes} will be available during snippet
|
||||||
|
* generation. Links will be extracted from the response automatically based on its
|
||||||
|
* content type.
|
||||||
|
*
|
||||||
|
* @param attributes Attributes made available during rendering of the links snippet
|
||||||
|
* @param descriptors The descriptions of the response's links
|
||||||
|
* @return the handler
|
||||||
|
*/
|
||||||
|
public static Snippet links(Map<String, Object> attributes,
|
||||||
|
LinkDescriptor... descriptors) {
|
||||||
|
return new LinksSnippet(new ContentTypeLinkExtractor(), attributes,
|
||||||
|
Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the links in the API
|
||||||
|
* call's response. Links will be extracted from the response using the given
|
||||||
|
* {@code linkExtractor}.
|
||||||
|
*
|
||||||
|
* @param linkExtractor Used to extract the links from the response
|
||||||
|
* @param descriptors The descriptions of the response's links
|
||||||
|
* @return the handler
|
||||||
|
*/
|
||||||
|
public static Snippet links(LinkExtractor linkExtractor,
|
||||||
|
LinkDescriptor... descriptors) {
|
||||||
|
return new LinksSnippet(linkExtractor, Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the links in the API
|
||||||
|
* call's response. The given {@code attributes} will be available during snippet
|
||||||
|
* generation. Links will be extracted from the response using the given
|
||||||
|
* {@code linkExtractor}.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
|
||||||
* @param attributes Attributes made available during rendering of the links snippet
|
* @param attributes Attributes made available during rendering of the links snippet
|
||||||
* @param linkExtractor Used to extract the links from the response
|
* @param linkExtractor Used to extract the links from the response
|
||||||
* @param descriptors The descriptions of the response's links
|
* @param descriptors The descriptions of the response's links
|
||||||
* @return the handler
|
* @return the handler
|
||||||
* @see RestDocumentationResultHandler#withLinks(LinkDescriptor...)
|
|
||||||
* @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...)
|
|
||||||
*/
|
*/
|
||||||
public static LinkSnippetResultHandler documentLinks(String identifier,
|
public static Snippet links(LinkExtractor linkExtractor,
|
||||||
Map<String, Object> attributes, LinkExtractor linkExtractor,
|
Map<String, Object> attributes, LinkDescriptor... descriptors) {
|
||||||
LinkDescriptor... descriptors) {
|
return new LinksSnippet(linkExtractor, attributes,
|
||||||
return new LinkSnippetResultHandler(identifier, attributes, linkExtractor,
|
|
||||||
Arrays.asList(descriptors));
|
Arrays.asList(descriptors));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code LinkExtractor} capable of extracting links in Hypermedia
|
||||||
|
* Application Language (HAL) format where the links are found in a map named
|
||||||
|
* {@code _links}.
|
||||||
|
*
|
||||||
|
* @return The extract for HAL-style links
|
||||||
|
*/
|
||||||
|
public static LinkExtractor halLinks() {
|
||||||
|
return new HalLinkExtractor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code LinkExtractor} capable of extracting links in Atom format where
|
||||||
|
* the links are found in an array named {@code links}.
|
||||||
|
*
|
||||||
|
* @return The extractor for Atom-style links
|
||||||
|
*/
|
||||||
|
public static LinkExtractor atomLinks() {
|
||||||
|
return new AtomLinkExtractor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.hypermedia;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static factory methods providing a selection of {@link LinkExtractor link extractors}
|
|
||||||
* for use when documentating a hypermedia-based API.
|
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
|
||||||
public abstract class LinkExtractors {
|
|
||||||
|
|
||||||
private LinkExtractors() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@code LinkExtractor} capable of extracting links in Hypermedia
|
|
||||||
* Application Language (HAL) format where the links are found in a map named
|
|
||||||
* {@code _links}.
|
|
||||||
*
|
|
||||||
* @return The extract for HAL-style links
|
|
||||||
*/
|
|
||||||
public static LinkExtractor halLinks() {
|
|
||||||
return new HalLinkExtractor();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@code LinkExtractor} capable of extracting links in Atom format where
|
|
||||||
* the links are found in an array named {@code links}.
|
|
||||||
*
|
|
||||||
* @return The extractor for Atom-style links
|
|
||||||
*/
|
|
||||||
public static LinkExtractor atomLinks() {
|
|
||||||
return new AtomLinkExtractor();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@code LinkExtractor} for the given {@code contentType} or {@code null}
|
|
||||||
* if there is no extractor for the content type.
|
|
||||||
*
|
|
||||||
* @param contentType The content type, may include parameters
|
|
||||||
* @return The extractor for the content type, or {@code null}
|
|
||||||
*/
|
|
||||||
public static LinkExtractor extractorForContentType(String contentType) {
|
|
||||||
if (StringUtils.hasText(contentType)) {
|
|
||||||
MediaType mediaType = MediaType.parseMediaType(contentType);
|
|
||||||
if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
|
|
||||||
return atomLinks();
|
|
||||||
}
|
|
||||||
if (mediaType.isCompatibleWith(HalLinkExtractor.HAL_MEDIA_TYPE)) {
|
|
||||||
return halLinks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract static class JsonContentLinkExtractor implements LinkExtractor {
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
|
||||||
throws IOException {
|
|
||||||
Map<String, Object> jsonContent = this.objectMapper.readValue(
|
|
||||||
response.getContentAsString(), Map.class);
|
|
||||||
return extractLinks(jsonContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Map<String, List<Link>> extractLinks(Map<String, Object> json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
static class HalLinkExtractor extends JsonContentLinkExtractor {
|
|
||||||
|
|
||||||
private static final MediaType HAL_MEDIA_TYPE = new MediaType("application",
|
|
||||||
"hal+json");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
|
||||||
Map<String, List<Link>> extractedLinks = new LinkedHashMap<>();
|
|
||||||
Object possibleLinks = json.get("_links");
|
|
||||||
if (possibleLinks instanceof Map) {
|
|
||||||
Map<String, Object> links = (Map<String, Object>) possibleLinks;
|
|
||||||
for (Entry<String, Object> entry : links.entrySet()) {
|
|
||||||
String rel = entry.getKey();
|
|
||||||
extractedLinks.put(rel, convertToLinks(entry.getValue(), rel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return extractedLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Link> convertToLinks(Object object, String rel) {
|
|
||||||
List<Link> links = new ArrayList<>();
|
|
||||||
if (object instanceof Collection) {
|
|
||||||
Collection<Object> hrefObjects = (Collection<Object>) object;
|
|
||||||
for (Object hrefObject : hrefObjects) {
|
|
||||||
maybeAddLink(maybeCreateLink(rel, hrefObject), links);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
maybeAddLink(maybeCreateLink(rel, object), links);
|
|
||||||
}
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Link maybeCreateLink(String rel, Object possibleHref) {
|
|
||||||
if (possibleHref instanceof String) {
|
|
||||||
return new Link(rel, (String) possibleHref);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void maybeAddLink(Link possibleLink, List<Link> links) {
|
|
||||||
if (possibleLink != null) {
|
|
||||||
links.add(possibleLink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
static class AtomLinkExtractor extends JsonContentLinkExtractor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
|
||||||
MultiValueMap<String, Link> extractedLinks = new LinkedMultiValueMap<>();
|
|
||||||
Object possibleLinks = json.get("links");
|
|
||||||
if (possibleLinks instanceof Collection) {
|
|
||||||
Collection<Object> linksCollection = (Collection<Object>) possibleLinks;
|
|
||||||
for (Object linkObject : linksCollection) {
|
|
||||||
if (linkObject instanceof Map) {
|
|
||||||
Link link = maybeCreateLink((Map<String, Object>) linkObject);
|
|
||||||
maybeStoreLink(link, extractedLinks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return extractedLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Link maybeCreateLink(Map<String, Object> linkMap) {
|
|
||||||
Object hrefObject = linkMap.get("href");
|
|
||||||
Object relObject = linkMap.get("rel");
|
|
||||||
if (relObject instanceof String && hrefObject instanceof String) {
|
|
||||||
return new Link((String) relObject, (String) hrefObject);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void maybeStoreLink(Link link,
|
|
||||||
MultiValueMap<String, Link> extractedLinks) {
|
|
||||||
if (link != null) {
|
|
||||||
extractedLinks.add(link.getRel(), link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,29 +26,33 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful
|
* A {@link Snippet} that documents a RESTful resource's links.
|
||||||
* resource's links.
|
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
class LinksSnippet extends TemplatedSnippet {
|
||||||
|
|
||||||
private final Map<String, LinkDescriptor> descriptorsByRel = new LinkedHashMap<>();
|
private final Map<String, LinkDescriptor> descriptorsByRel = new LinkedHashMap<>();
|
||||||
|
|
||||||
private final Set<String> requiredRels = new HashSet<>();
|
private final Set<String> requiredRels = new HashSet<>();
|
||||||
|
|
||||||
private final LinkExtractor extractor;
|
private final LinkExtractor linkExtractor;
|
||||||
|
|
||||||
LinkSnippetResultHandler(String identifier, Map<String, Object> attributes,
|
LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors) {
|
||||||
LinkExtractor linkExtractor, List<LinkDescriptor> descriptors) {
|
this(linkExtractor, null, descriptors);
|
||||||
super(identifier, "links", attributes);
|
}
|
||||||
this.extractor = linkExtractor;
|
|
||||||
|
LinksSnippet(LinkExtractor linkExtractor, Map<String, Object> attributes,
|
||||||
|
List<LinkDescriptor> descriptors) {
|
||||||
|
super("links", attributes);
|
||||||
|
this.linkExtractor = linkExtractor;
|
||||||
for (LinkDescriptor descriptor : descriptors) {
|
for (LinkDescriptor descriptor : descriptors) {
|
||||||
Assert.hasText(descriptor.getRel());
|
Assert.hasText(descriptor.getRel());
|
||||||
Assert.hasText(descriptor.getDescription());
|
Assert.hasText(descriptor.getDescription());
|
||||||
@@ -60,31 +64,13 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, Object> doHandle(MvcResult result) throws IOException {
|
protected Map<String, Object> document(MvcResult result) throws IOException {
|
||||||
validate(extractLinks(result));
|
validate(this.linkExtractor.extractLinks(result.getResponse()));
|
||||||
Map<String, Object> model = new HashMap<>();
|
Map<String, Object> model = new HashMap<>();
|
||||||
model.put("links", createLinksModel());
|
model.put("links", createLinksModel());
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, List<Link>> extractLinks(MvcResult result) throws IOException {
|
|
||||||
if (this.extractor != null) {
|
|
||||||
return this.extractor.extractLinks(result.getResponse());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
String contentType = result.getResponse().getContentType();
|
|
||||||
LinkExtractor extractorForContentType = LinkExtractors
|
|
||||||
.extractorForContentType(contentType);
|
|
||||||
if (extractorForContentType != null) {
|
|
||||||
return extractorForContentType.extractLinks(result.getResponse());
|
|
||||||
}
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"No LinkExtractor has been provided and one is not available for the content type "
|
|
||||||
+ contentType);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validate(Map<String, List<Link>> links) {
|
private void validate(Map<String, List<Link>> links) {
|
||||||
Set<String> actualRels = links.keySet();
|
Set<String> actualRels = links.keySet();
|
||||||
|
|
||||||
@@ -104,10 +90,10 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
|||||||
if (message.length() > 0) {
|
if (message.length() > 0) {
|
||||||
message += ". ";
|
message += ". ";
|
||||||
}
|
}
|
||||||
message += "Links with the following relations were not found in the response: "
|
message += "Links with the following relations were not found in the "
|
||||||
+ missingRels;
|
+ "response: " + missingRels;
|
||||||
}
|
}
|
||||||
throw new SnippetGenerationException(message);
|
throw new SnippetException(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,20 +25,21 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful
|
* A {@link TemplatedSnippet} that produces a snippet documenting a
|
||||||
* resource's request or response fields.
|
* RESTful resource's request or response fields.
|
||||||
*
|
*
|
||||||
* @author Andreas Evers
|
* @author Andreas Evers
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public abstract class FieldSnippetResultHandler extends SnippetWritingResultHandler {
|
public abstract class AbstractFieldsSnippet extends
|
||||||
|
TemplatedSnippet {
|
||||||
|
|
||||||
private final Map<String, FieldDescriptor> descriptorsByPath = new LinkedHashMap<String, FieldDescriptor>();
|
private final Map<String, FieldDescriptor> descriptorsByPath = new LinkedHashMap<String, FieldDescriptor>();
|
||||||
|
|
||||||
@@ -50,9 +51,9 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
|||||||
|
|
||||||
private List<FieldDescriptor> fieldDescriptors;
|
private List<FieldDescriptor> fieldDescriptors;
|
||||||
|
|
||||||
FieldSnippetResultHandler(String identifier, String type,
|
AbstractFieldsSnippet(String type, Map<String, Object> attributes,
|
||||||
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
List<FieldDescriptor> descriptors) {
|
||||||
super(identifier, type + "-fields", attributes);
|
super(type + "-fields", attributes);
|
||||||
for (FieldDescriptor descriptor : descriptors) {
|
for (FieldDescriptor descriptor : descriptors) {
|
||||||
Assert.notNull(descriptor.getPath());
|
Assert.notNull(descriptor.getPath());
|
||||||
Assert.hasText(descriptor.getDescription());
|
Assert.hasText(descriptor.getDescription());
|
||||||
@@ -62,7 +63,7 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, Object> doHandle(MvcResult result) throws IOException {
|
protected Map<String, Object> document(MvcResult result) throws IOException {
|
||||||
this.fieldValidator.validate(getPayloadReader(result), this.fieldDescriptors);
|
this.fieldValidator.validate(getPayloadReader(result), this.fieldDescriptors);
|
||||||
Object payload = extractPayload(result);
|
Object payload = extractPayload(result);
|
||||||
Map<String, Object> model = new HashMap<>();
|
Map<String, Object> model = new HashMap<>();
|
||||||
@@ -80,8 +81,8 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
|||||||
|
|
||||||
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
||||||
try {
|
try {
|
||||||
return FieldSnippetResultHandler.this.fieldTypeResolver.resolveFieldType(
|
return AbstractFieldsSnippet.this.fieldTypeResolver
|
||||||
descriptor.getPath(), payload);
|
.resolveFieldType(descriptor.getPath(), payload);
|
||||||
}
|
}
|
||||||
catch (FieldDoesNotExistException ex) {
|
catch (FieldDoesNotExistException ex) {
|
||||||
String message = "Cannot determine the type of the field '"
|
String message = "Cannot determine the type of the field '"
|
||||||
@@ -22,7 +22,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
@@ -60,7 +60,7 @@ class FieldValidator {
|
|||||||
message += "Fields with the following paths were not found in the payload: "
|
message += "Fields with the following paths were not found in the payload: "
|
||||||
+ missingFields;
|
+ missingFields;
|
||||||
}
|
}
|
||||||
throw new SnippetGenerationException(message);
|
throw new SnippetException(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package org.springframework.restdocs.payload;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.restdocs.RestDocumentationResultHandler;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory methods for documenting a RESTful API's request and response payloads.
|
* Static factory methods for documenting a RESTful API's request and response payloads.
|
||||||
@@ -89,57 +89,92 @@ public abstract class PayloadDocumentation {
|
|||||||
*
|
*
|
||||||
* @param path The path of the field
|
* @param path The path of the field
|
||||||
* @return a {@code FieldDescriptor} ready for further configuration
|
* @return a {@code FieldDescriptor} ready for further configuration
|
||||||
* @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...)
|
|
||||||
* @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...)
|
|
||||||
*/
|
*/
|
||||||
public static FieldDescriptor fieldWithPath(String path) {
|
public static FieldDescriptor fieldWithPath(String path) {
|
||||||
return new FieldDescriptor(path);
|
return new FieldDescriptor(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code RequestFieldsSnippetResultHandler} that will produce a
|
* Returns a handler that will produce a snippet documenting the fields of the API
|
||||||
* documentation snippet for a request's fields.
|
* call's request.
|
||||||
* <p>
|
* <p>
|
||||||
* If a field is present in the request but is not documented by one of the
|
* If a field is present in the request, but is not documented by one of the
|
||||||
* descriptors a failure will occur when the handler is invoked. Similarly, if a field
|
* descriptors, a failure will occur when the handler is invoked. Similarly, if a
|
||||||
* is documented, is not marked as optional, and is not present in the request a
|
* field is documented, is not marked as optional, and is not present in the request,
|
||||||
* failure will also occur. For payloads with a hierarchical structure, documenting a
|
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||||
* field is sufficient for all of its descendants to also be treated as having been
|
* a field is sufficient for all of its descendants to also be treated as having been
|
||||||
* documented.
|
* documented.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
|
||||||
* @param attributes Attributes made available during rendering of the links snippet
|
|
||||||
* @param descriptors The descriptions of the request's fields
|
* @param descriptors The descriptions of the request's fields
|
||||||
* @return the handler
|
* @return the handler
|
||||||
* @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...)
|
|
||||||
* @see #fieldWithPath(String)
|
* @see #fieldWithPath(String)
|
||||||
*/
|
*/
|
||||||
public static FieldSnippetResultHandler documentRequestFields(String identifier,
|
public static Snippet requestFields(FieldDescriptor... descriptors) {
|
||||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
return new RequestFieldsSnippet(Arrays.asList(descriptors));
|
||||||
return new RequestFieldSnippetResultHandler(identifier, attributes,
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the fields of the API
|
||||||
|
* call's request. The given {@code attributes} will be available during snippet
|
||||||
|
* generation.
|
||||||
|
* <p>
|
||||||
|
* If a field is present in the request, but is not documented by one of the
|
||||||
|
* descriptors, a failure will occur when the handler is invoked. Similarly, if a
|
||||||
|
* field is documented, is not marked as optional, and is not present in the request,
|
||||||
|
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||||
|
* a field is sufficient for all of its descendants to also be treated as having been
|
||||||
|
* documented.
|
||||||
|
*
|
||||||
|
* @param attributes Attributes made available during rendering of the snippet
|
||||||
|
* @param descriptors The descriptions of the request's fields
|
||||||
|
* @return the handler
|
||||||
|
* @see #fieldWithPath(String)
|
||||||
|
*/
|
||||||
|
public static Snippet requestFields(Map<String, Object> attributes,
|
||||||
|
FieldDescriptor... descriptors) {
|
||||||
|
return new RequestFieldsSnippet(attributes,
|
||||||
Arrays.asList(descriptors));
|
Arrays.asList(descriptors));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code ResponseFieldsSnippetResultHandler} that will produce a
|
* Returns a handler that will produce a snippet documenting the fields of the API
|
||||||
* documentation snippet for a response's fields.
|
* call's response.
|
||||||
* <p>
|
* <p>
|
||||||
* If a field is present in the response but is not documented by one of the
|
* If a field is present in the response, but is not documented by one of the
|
||||||
* descriptors a failure will occur when the handler is invoked. Similarly, if a field
|
* descriptors, a failure will occur when the handler is invoked. Similarly, if a
|
||||||
* is documented, is not marked as optional, and is not present in the response a
|
* field is documented, is not marked as optional, and is not present in the response,
|
||||||
* failure will also occur. For payloads with a hierarchical structure, documenting a
|
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||||
* field is sufficient for all of its descendants to also be treated as having been
|
* a field is sufficient for all of its descendants to also be treated as having been
|
||||||
* documented.
|
* documented.
|
||||||
*
|
*
|
||||||
* @param identifier An identifier for the API call that is being documented
|
|
||||||
* @param attributes Attributes made available during rendering of the links snippet
|
|
||||||
* @param descriptors The descriptions of the response's fields
|
* @param descriptors The descriptions of the response's fields
|
||||||
* @return the handler
|
* @return the handler
|
||||||
* @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...)
|
* @see #fieldWithPath(String)
|
||||||
*/
|
*/
|
||||||
public static FieldSnippetResultHandler documentResponseFields(String identifier,
|
public static Snippet responseFields(FieldDescriptor... descriptors) {
|
||||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
return new ResponseFieldsSnippet(Arrays.asList(descriptors));
|
||||||
return new ResponseFieldSnippetResultHandler(identifier, attributes,
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the fields of the API
|
||||||
|
* call's response. The given {@code attributes} will be available during snippet
|
||||||
|
* generation.
|
||||||
|
* <p>
|
||||||
|
* If a field is present in the response, but is not documented by one of the
|
||||||
|
* descriptors, a failure will occur when the handler is invoked. Similarly, if a
|
||||||
|
* field is documented, is not marked as optional, and is not present in the response,
|
||||||
|
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||||
|
* a field is sufficient for all of its descendants to also be treated as having been
|
||||||
|
* documented.
|
||||||
|
*
|
||||||
|
* @param attributes Attributes made available during rendering of the snippet
|
||||||
|
* @param descriptors The descriptions of the response's fields
|
||||||
|
* @return the handler
|
||||||
|
* @see #fieldWithPath(String)
|
||||||
|
*/
|
||||||
|
public static Snippet responseFields(Map<String, Object> attributes,
|
||||||
|
FieldDescriptor... descriptors) {
|
||||||
|
return new ResponseFieldsSnippet(attributes,
|
||||||
Arrays.asList(descriptors));
|
Arrays.asList(descriptors));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,18 +20,22 @@ import java.io.Reader;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FieldSnippetResultHandler} for documenting a request's fields
|
* A {@link Snippet} that documents the fields in a request.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class RequestFieldSnippetResultHandler extends FieldSnippetResultHandler {
|
class RequestFieldsSnippet extends AbstractFieldsSnippet {
|
||||||
|
|
||||||
RequestFieldSnippetResultHandler(String identifier, Map<String, Object> attributes,
|
RequestFieldsSnippet(List<FieldDescriptor> descriptors) {
|
||||||
List<FieldDescriptor> descriptors) {
|
this(null, descriptors);
|
||||||
super(identifier, "request", attributes, descriptors);
|
}
|
||||||
|
|
||||||
|
RequestFieldsSnippet(Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||||
|
super("request", attributes, descriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -21,18 +21,23 @@ import java.io.StringReader;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FieldSnippetResultHandler} for documenting a response's fields
|
* A {@link Snippet} the documents the fields in a response.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class ResponseFieldSnippetResultHandler extends FieldSnippetResultHandler {
|
class ResponseFieldsSnippet extends AbstractFieldsSnippet {
|
||||||
|
|
||||||
ResponseFieldSnippetResultHandler(String identifier, Map<String, Object> attributes,
|
ResponseFieldsSnippet(List<FieldDescriptor> descriptors) {
|
||||||
|
this(null, descriptors);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseFieldsSnippet(Map<String, Object> attributes,
|
||||||
List<FieldDescriptor> descriptors) {
|
List<FieldDescriptor> descriptors) {
|
||||||
super(identifier, "response", attributes, descriptors);
|
super("response", attributes, descriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -10,19 +10,18 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
public abstract class AbstractParametersSnippetResultHandler extends
|
abstract class AbstractParametersSnippet extends
|
||||||
SnippetWritingResultHandler {
|
TemplatedSnippet {
|
||||||
|
|
||||||
private final Map<String, ParameterDescriptor> descriptorsByName = new LinkedHashMap<>();
|
private final Map<String, ParameterDescriptor> descriptorsByName = new LinkedHashMap<>();
|
||||||
|
|
||||||
protected AbstractParametersSnippetResultHandler(String identifier,
|
protected AbstractParametersSnippet(String snippetName,
|
||||||
String snippetName, Map<String, Object> attributes,
|
Map<String, Object> attributes, List<ParameterDescriptor> descriptors) {
|
||||||
ParameterDescriptor... descriptors) {
|
super(snippetName, attributes);
|
||||||
super(identifier, snippetName, attributes);
|
|
||||||
for (ParameterDescriptor descriptor : descriptors) {
|
for (ParameterDescriptor descriptor : descriptors) {
|
||||||
Assert.hasText(descriptor.getName());
|
Assert.hasText(descriptor.getName());
|
||||||
Assert.hasText(descriptor.getDescription());
|
Assert.hasText(descriptor.getDescription());
|
||||||
@@ -31,7 +30,7 @@ public abstract class AbstractParametersSnippetResultHandler extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, Object> doHandle(MvcResult result) throws IOException {
|
protected Map<String, Object> document(MvcResult result) throws IOException {
|
||||||
verifyParameterDescriptors(result);
|
verifyParameterDescriptors(result);
|
||||||
|
|
||||||
Map<String, Object> model = new HashMap<>();
|
Map<String, Object> model = new HashMap<>();
|
||||||
@@ -17,30 +17,33 @@
|
|||||||
package org.springframework.restdocs.request;
|
package org.springframework.restdocs.request;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting the path
|
* A {@link Snippet} that documents the path parameters supported by a RESTful resource.
|
||||||
* parameters supported by a RESTful resource.
|
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class PathParametersSnippetResultHandler extends
|
class PathParametersSnippet extends AbstractParametersSnippet {
|
||||||
AbstractParametersSnippetResultHandler {
|
|
||||||
|
|
||||||
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
|
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
|
||||||
|
|
||||||
protected PathParametersSnippetResultHandler(String identifier,
|
PathParametersSnippet(List<ParameterDescriptor> descriptors) {
|
||||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
this(null, descriptors);
|
||||||
super(identifier, "path-parameters", attributes, descriptors);
|
}
|
||||||
|
|
||||||
|
PathParametersSnippet(Map<String, Object> attributes,
|
||||||
|
List<ParameterDescriptor> descriptors) {
|
||||||
|
super("path-parameters", attributes, descriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,7 +87,7 @@ public class PathParametersSnippetResultHandler extends
|
|||||||
message += "Path parameters with the following names were not found in "
|
message += "Path parameters with the following names were not found in "
|
||||||
+ "the request: " + missingParameters;
|
+ "the request: " + missingParameters;
|
||||||
}
|
}
|
||||||
throw new SnippetGenerationException(message);
|
throw new SnippetException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,25 +16,28 @@
|
|||||||
|
|
||||||
package org.springframework.restdocs.request;
|
package org.springframework.restdocs.request;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting the query
|
* A {@link Snippet} that documents the query parameters supported by a RESTful resource.
|
||||||
* parameters supported by a RESTful resource.
|
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class QueryParametersSnippetResultHandler extends
|
class QueryParametersSnippet extends AbstractParametersSnippet {
|
||||||
AbstractParametersSnippetResultHandler {
|
|
||||||
|
|
||||||
protected QueryParametersSnippetResultHandler(String identifier,
|
QueryParametersSnippet(List<ParameterDescriptor> descriptors) {
|
||||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
this(null, descriptors);
|
||||||
super(identifier, "query-parameters", attributes, descriptors);
|
}
|
||||||
|
|
||||||
|
QueryParametersSnippet(Map<String, Object> attributes,
|
||||||
|
List<ParameterDescriptor> descriptors) {
|
||||||
|
super("query-parameters", attributes, descriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -52,7 +55,7 @@ public class QueryParametersSnippetResultHandler extends
|
|||||||
message += "Query parameters with the following names were not found in the request: "
|
message += "Query parameters with the following names were not found in the request: "
|
||||||
+ missingParameters;
|
+ missingParameters;
|
||||||
}
|
}
|
||||||
throw new SnippetGenerationException(message);
|
throw new SnippetException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package org.springframework.restdocs.request;
|
package org.springframework.restdocs.request;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.restdocs.RestDocumentationResultHandler;
|
import org.springframework.restdocs.snippet.Snippet;
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory methods for documenting aspects of a request sent to a RESTful API.
|
* Static factory methods for documenting aspects of a request sent to a RESTful API.
|
||||||
@@ -32,49 +32,69 @@ 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<String, Object> attributes, ParameterDescriptor... descriptors) {
|
|
||||||
return new PathParametersSnippetResultHandler(identifier, attributes, descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link SnippetWritingResultHandler} that will produce a snippet
|
|
||||||
* documenting a request's query parameters
|
|
||||||
*
|
|
||||||
* @param identifier An identifier for the API call that is being documented
|
|
||||||
* @param attributes Attributes made available during rendering of the query
|
|
||||||
* parameters snippet
|
|
||||||
* @param descriptors The descriptions of the parameters in the request's query string
|
|
||||||
* @return the result handler
|
|
||||||
* @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...)
|
|
||||||
*/
|
|
||||||
public static SnippetWritingResultHandler documentQueryParameters(String identifier,
|
|
||||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
|
||||||
return new QueryParametersSnippetResultHandler(identifier, attributes,
|
|
||||||
descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ParameterDescriptor} that describes a query string parameter with
|
* Creates a {@link ParameterDescriptor} that describes a query string parameter with
|
||||||
* the given {@code name}.
|
* the given {@code name}.
|
||||||
*
|
*
|
||||||
* @param name The name of the parameter
|
* @param name The name of the parameter
|
||||||
* @return a {@link ParameterDescriptor} ready for further configuration
|
* @return a {@link ParameterDescriptor} ready for further configuration
|
||||||
* @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...)
|
|
||||||
*/
|
*/
|
||||||
public static ParameterDescriptor parameterWithName(String name) {
|
public static ParameterDescriptor parameterWithName(String name) {
|
||||||
return new ParameterDescriptor(name);
|
return new ParameterDescriptor(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the path parameters from
|
||||||
|
* the API call's request.
|
||||||
|
*
|
||||||
|
* @param descriptors The descriptions of the parameters in the request's path
|
||||||
|
* @return the handler
|
||||||
|
*/
|
||||||
|
public static Snippet pathParameters(ParameterDescriptor... descriptors) {
|
||||||
|
return new PathParametersSnippet(Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the path parameters from
|
||||||
|
* the API call's request. The given {@code attributes} will be available during
|
||||||
|
* snippet generation.
|
||||||
|
*
|
||||||
|
* @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 handler
|
||||||
|
*/
|
||||||
|
public static Snippet pathParameters(Map<String, Object> attributes,
|
||||||
|
ParameterDescriptor... descriptors) {
|
||||||
|
return new PathParametersSnippet(attributes,
|
||||||
|
Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the query parameters from
|
||||||
|
* the API call's request.
|
||||||
|
*
|
||||||
|
* @param descriptors The descriptions of the request's query parameters
|
||||||
|
* @return the handler
|
||||||
|
*/
|
||||||
|
public static Snippet queryParameters(ParameterDescriptor... descriptors) {
|
||||||
|
return new QueryParametersSnippet(Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a handler that will produce a snippet documenting the query parameters from
|
||||||
|
* the API call's request. The given {@code attributes} will be available during
|
||||||
|
* snippet generation.
|
||||||
|
*
|
||||||
|
* @param attributes Attributes made available during rendering of the query
|
||||||
|
* parameters snippet
|
||||||
|
* @param descriptors The descriptions of the request's query parameters
|
||||||
|
* @return the handler
|
||||||
|
*/
|
||||||
|
public static Snippet queryParameters(Map<String, Object> attributes,
|
||||||
|
ParameterDescriptor... descriptors) {
|
||||||
|
return new QueryParametersSnippet(attributes,
|
||||||
|
Arrays.asList(descriptors));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public abstract class ContentModifyingReponsePostProcessor implements
|
|||||||
|
|
||||||
private final MockHttpServletResponse delegate;
|
private final MockHttpServletResponse delegate;
|
||||||
|
|
||||||
public ContentModifyingMethodInterceptor(String modifiedContent,
|
private ContentModifyingMethodInterceptor(String modifiedContent,
|
||||||
MockHttpServletResponse delegate) {
|
MockHttpServletResponse delegate) {
|
||||||
this.modifiedContent = modifiedContent;
|
this.modifiedContent = modifiedContent;
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class HeaderRemovingResponsePostProcessor implements ResponsePostProcessor {
|
|||||||
|
|
||||||
private final Set<String> headersToRemove;
|
private final Set<String> headersToRemove;
|
||||||
|
|
||||||
public HeaderRemovingResponsePostProcessor(String... headersToRemove) {
|
HeaderRemovingResponsePostProcessor(String... headersToRemove) {
|
||||||
this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove));
|
this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,12 +75,11 @@ public abstract class Attributes {
|
|||||||
public Attribute value(Object value) {
|
public Attribute value(Object value) {
|
||||||
return new Attribute(this.key, value);
|
return new Attribute(this.key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute (key-value pair).
|
* An attribute (key-value pair).
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
*/
|
||||||
public static class Attribute {
|
public static class Attribute {
|
||||||
|
|
||||||
@@ -88,18 +87,32 @@ public abstract class Attributes {
|
|||||||
|
|
||||||
private final Object value;
|
private final Object value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new attribute with the given {@code key} and {@code value}.
|
||||||
|
* @param key the key
|
||||||
|
* @param value the value
|
||||||
|
*/
|
||||||
public Attribute(String key, Object value) {
|
public Attribute(String key, Object value) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the attribute's key
|
||||||
|
* @return the key
|
||||||
|
*/
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return this.key;
|
return this.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the attribute's value
|
||||||
|
* @return the value
|
||||||
|
*/
|
||||||
public Object getValue() {
|
public Object getValue() {
|
||||||
return this.value;
|
return this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.snippet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Snippet} is used to document aspects of a call to a RESTful API.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public interface Snippet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Documents the call to the RESTful API described by the given {@code result}. The
|
||||||
|
* call is identified by the given {@code operation}.
|
||||||
|
*
|
||||||
|
* @param operation the API operation
|
||||||
|
* @param result the result of the operation
|
||||||
|
* @throws IOException if a failure occurs will documenting the result
|
||||||
|
*/
|
||||||
|
void document(String operation, MvcResult result) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,14 +23,13 @@ package org.springframework.restdocs.snippet;
|
|||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class SnippetGenerationException extends RuntimeException {
|
public class SnippetException extends RuntimeException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code SnippetGenerationException} described by the given
|
* Creates a new {@code SnippetException} described by the given {@code message}
|
||||||
* {@code message}
|
|
||||||
* @param message the message that describes the problem
|
* @param message the message that describes the problem
|
||||||
*/
|
*/
|
||||||
public SnippetGenerationException(String message) {
|
public SnippetException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,26 +21,23 @@ import java.io.Writer;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.restdocs.templates.Template;
|
||||||
import org.springframework.restdocs.templates.TemplateEngine;
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.test.web.servlet.ResultHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for a {@link ResultHandler} that writes a documentation snippet
|
* Base class for a {@link Snippet} that is produced using a {@link Template} and
|
||||||
|
* {@link TemplateEngine}.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public abstract class SnippetWritingResultHandler implements ResultHandler {
|
public abstract class TemplatedSnippet implements Snippet {
|
||||||
|
|
||||||
private final Map<String, Object> attributes = new HashMap<>();
|
private final Map<String, Object> attributes = new HashMap<>();
|
||||||
|
|
||||||
private final String identifier;
|
|
||||||
|
|
||||||
private final String snippetName;
|
private final String snippetName;
|
||||||
|
|
||||||
protected SnippetWritingResultHandler(String identifier, String snippetName,
|
protected TemplatedSnippet(String snippetName, Map<String, Object> attributes) {
|
||||||
Map<String, Object> attributes) {
|
|
||||||
this.identifier = identifier;
|
|
||||||
this.snippetName = snippetName;
|
this.snippetName = snippetName;
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
this.attributes.putAll(attributes);
|
this.attributes.putAll(attributes);
|
||||||
@@ -48,11 +45,11 @@ public abstract class SnippetWritingResultHandler implements ResultHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MvcResult result) throws IOException {
|
public void document(String operation, MvcResult result) throws IOException {
|
||||||
WriterResolver writerResolver = (WriterResolver) result.getRequest()
|
WriterResolver writerResolver = (WriterResolver) result.getRequest()
|
||||||
.getAttribute(WriterResolver.class.getName());
|
.getAttribute(WriterResolver.class.getName());
|
||||||
try (Writer writer = writerResolver.resolve(this.identifier, this.snippetName)) {
|
try (Writer writer = writerResolver.resolve(operation, this.snippetName)) {
|
||||||
Map<String, Object> model = doHandle(result);
|
Map<String, Object> model = document(result);
|
||||||
model.putAll(this.attributes);
|
model.putAll(this.attributes);
|
||||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||||
.getAttribute(TemplateEngine.class.getName());
|
.getAttribute(TemplateEngine.class.getName());
|
||||||
@@ -60,6 +57,6 @@ public abstract class SnippetWritingResultHandler implements ResultHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Map<String, Object> doHandle(MvcResult result) throws IOException;
|
protected abstract Map<String, Object> document(MvcResult result) throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,15 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import static org.springframework.restdocs.RestDocumentation.document;
|
import static org.springframework.restdocs.RestDocumentation.document;
|
||||||
import static org.springframework.restdocs.RestDocumentation.modifyResponseTo;
|
import static org.springframework.restdocs.RestDocumentation.modifyResponseTo;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
|
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
|
||||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
||||||
|
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
|
||||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||||
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
|
||||||
import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks;
|
import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks;
|
||||||
import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent;
|
import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent;
|
||||||
import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders;
|
import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders;
|
||||||
@@ -112,14 +118,14 @@ public class RestDocumentationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void links() throws Exception {
|
public void linksSnippet() throws Exception {
|
||||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||||
.apply(new RestDocumentationConfigurer()).build();
|
.apply(new RestDocumentationConfigurer()).build();
|
||||||
|
|
||||||
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("links").withLinks(
|
.andDo(document("links",
|
||||||
linkWithRel("rel").description("The description")));
|
links(linkWithRel("rel").description("The description"))));
|
||||||
|
|
||||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||||
@@ -127,14 +133,14 @@ public class RestDocumentationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void pathParameters() throws Exception {
|
public void pathParametersSnippet() throws Exception {
|
||||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||||
.apply(new RestDocumentationConfigurer()).build();
|
.apply(new RestDocumentationConfigurer()).build();
|
||||||
|
|
||||||
mockMvc.perform(get("{foo}", "/").accept(MediaType.APPLICATION_JSON))
|
mockMvc.perform(get("{foo}", "/").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("links").withPathParameters(
|
.andDo(document("links", pathParameters(parameterWithName("foo")
|
||||||
parameterWithName("foo").description("The description")));
|
.description("The description"))));
|
||||||
|
|
||||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||||
@@ -142,14 +148,14 @@ public class RestDocumentationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void queryParameters() throws Exception {
|
public void queryParametersSnippet() throws Exception {
|
||||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||||
.apply(new RestDocumentationConfigurer()).build();
|
.apply(new RestDocumentationConfigurer()).build();
|
||||||
|
|
||||||
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("links").withQueryParameters(
|
.andDo(document("links", queryParameters(parameterWithName("foo")
|
||||||
parameterWithName("foo").description("The description")));
|
.description("The description"))));
|
||||||
|
|
||||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||||
@@ -157,7 +163,7 @@ public class RestDocumentationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestFields() throws Exception {
|
public void requestFieldsSnippet() throws Exception {
|
||||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||||
.apply(new RestDocumentationConfigurer()).build();
|
.apply(new RestDocumentationConfigurer()).build();
|
||||||
|
|
||||||
@@ -165,8 +171,8 @@ public class RestDocumentationIntegrationTests {
|
|||||||
get("/").param("foo", "bar").content("{\"a\":\"alpha\"}")
|
get("/").param("foo", "bar").content("{\"a\":\"alpha\"}")
|
||||||
.accept(MediaType.APPLICATION_JSON))
|
.accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("links").withRequestFields(
|
.andDo(document("links",
|
||||||
fieldWithPath("a").description("The description")));
|
requestFields(fieldWithPath("a").description("The description"))));
|
||||||
|
|
||||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||||
@@ -174,15 +180,18 @@ public class RestDocumentationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void responseFields() throws Exception {
|
public void responseFieldsSnippet() throws Exception {
|
||||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||||
.apply(new RestDocumentationConfigurer()).build();
|
.apply(new RestDocumentationConfigurer()).build();
|
||||||
|
|
||||||
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andDo(document("links").withResponseFields(
|
.andDo(document(
|
||||||
fieldWithPath("a").description("The description"),
|
"links",
|
||||||
fieldWithPath("links").description("Links to other resources")));
|
responseFields(
|
||||||
|
fieldWithPath("a").description("The description"),
|
||||||
|
fieldWithPath("links").description(
|
||||||
|
"Links to other resources"))));
|
||||||
|
|
||||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||||
@@ -284,8 +293,10 @@ public class RestDocumentationIntegrationTests {
|
|||||||
is(snippet().withContents(equalTo("Custom curl request"))));
|
is(snippet().withContents(equalTo("Custom curl request"))));
|
||||||
|
|
||||||
mockMvc.perform(get("/")).andDo(
|
mockMvc.perform(get("/")).andDo(
|
||||||
document("index").withCurlRequest(
|
document(
|
||||||
attributes(key("title").value("Access the index using curl"))));
|
"index",
|
||||||
|
curlRequest(attributes(key("title").value(
|
||||||
|
"Access the index using curl")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class RestDocumentationConfigurerTests {
|
|||||||
|
|
||||||
private MockHttpServletRequest request = new MockHttpServletRequest();
|
private MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
|
||||||
private RestDocumentationContext context = new RestDocumentationContext();
|
private RestDocumentationContext context = new RestDocumentationContext(null);
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void establishContext() {
|
public void establishContext() {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.springframework.restdocs.config;
|
||||||
|
|
||||||
|
|
||||||
|
public class RestDocumentationContexts {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@ import static org.springframework.restdocs.RestDocumentationRequestBuilders.file
|
|||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.put;
|
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.attributes;
|
||||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||||
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
|
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
|
||||||
@@ -44,14 +43,14 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
|||||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link CurlDocumentation}
|
* Tests for {@link CurlRequestSnippet}
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Yann Le Guern
|
* @author Yann Le Guern
|
||||||
* @author Dmitriy Mayboroda
|
* @author Dmitriy Mayboroda
|
||||||
* @author Jonathan Pearlin
|
* @author Jonathan Pearlin
|
||||||
*/
|
*/
|
||||||
public class CurlDocumentationTests {
|
public class CurlRequestDocumentationHandlerTests {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedSnippet snippet = new ExpectedSnippet();
|
public ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
@@ -63,14 +62,16 @@ public class CurlDocumentationTests {
|
|||||||
public void getRequest() throws IOException {
|
public void getRequest() throws IOException {
|
||||||
this.snippet.expectCurlRequest("get-request").withContents(
|
this.snippet.expectCurlRequest("get-request").withContents(
|
||||||
codeBlock("bash").content("$ curl 'http://localhost/foo' -i"));
|
codeBlock("bash").content("$ curl 'http://localhost/foo' -i"));
|
||||||
documentCurlRequest("get-request", null).handle(result(get("/foo")));
|
new CurlRequestSnippet()
|
||||||
|
.document("get-request", result(get("/foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nonGetRequest() throws IOException {
|
public void nonGetRequest() throws IOException {
|
||||||
this.snippet.expectCurlRequest("non-get-request").withContents(
|
this.snippet.expectCurlRequest("non-get-request").withContents(
|
||||||
codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST"));
|
codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST"));
|
||||||
documentCurlRequest("non-get-request", null).handle(result(post("/foo")));
|
new CurlRequestSnippet().document("non-get-request",
|
||||||
|
result(post("/foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -78,7 +79,7 @@ public class CurlDocumentationTests {
|
|||||||
this.snippet.expectCurlRequest("request-with-content").withContents(
|
this.snippet.expectCurlRequest("request-with-content").withContents(
|
||||||
codeBlock("bash")
|
codeBlock("bash")
|
||||||
.content("$ curl 'http://localhost/foo' -i -d 'content'"));
|
.content("$ curl 'http://localhost/foo' -i -d 'content'"));
|
||||||
documentCurlRequest("request-with-content", null).handle(
|
new CurlRequestSnippet().document("request-with-content",
|
||||||
result(get("/foo").content("content")));
|
result(get("/foo").content("content")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ public class CurlDocumentationTests {
|
|||||||
.withContents(
|
.withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo?param=value' -i"));
|
"$ curl 'http://localhost/foo?param=value' -i"));
|
||||||
documentCurlRequest("request-with-query-string", null).handle(
|
new CurlRequestSnippet().document("request-with-query-string",
|
||||||
result(get("/foo?param=value")));
|
result(get("/foo?param=value")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ public class CurlDocumentationTests {
|
|||||||
public void requestWithOneParameter() throws IOException {
|
public void requestWithOneParameter() throws IOException {
|
||||||
this.snippet.expectCurlRequest("request-with-one-parameter").withContents(
|
this.snippet.expectCurlRequest("request-with-one-parameter").withContents(
|
||||||
codeBlock("bash").content("$ curl 'http://localhost/foo?k1=v1' -i"));
|
codeBlock("bash").content("$ curl 'http://localhost/foo?k1=v1' -i"));
|
||||||
documentCurlRequest("request-with-one-parameter", null).handle(
|
new CurlRequestSnippet().document("request-with-one-parameter",
|
||||||
result(get("/foo").param("k1", "v1")));
|
result(get("/foo").param("k1", "v1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +106,9 @@ public class CurlDocumentationTests {
|
|||||||
this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents(
|
this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i"));
|
"$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i"));
|
||||||
documentCurlRequest("request-with-multiple-parameters", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
result(get("/foo").param("k1", "v1").param("k2", "v2")
|
"request-with-multiple-parameters", result(get("/foo").param("k1", "v1")
|
||||||
.param("k1", "v1-bis")));
|
.param("k2", "v2").param("k1", "v1-bis")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -116,7 +117,8 @@ public class CurlDocumentationTests {
|
|||||||
.withContents(
|
.withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo?k1=foo+bar%26' -i"));
|
"$ curl 'http://localhost/foo?k1=foo+bar%26' -i"));
|
||||||
documentCurlRequest("request-with-url-encoded-parameter", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"request-with-url-encoded-parameter",
|
||||||
result(get("/foo").param("k1", "foo bar&")));
|
result(get("/foo").param("k1", "foo bar&")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +127,7 @@ public class CurlDocumentationTests {
|
|||||||
this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents(
|
this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
|
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'"));
|
||||||
documentCurlRequest("post-request-with-one-parameter", null).handle(
|
new CurlRequestSnippet().document("post-request-with-one-parameter",
|
||||||
result(post("/foo").param("k1", "v1")));
|
result(post("/foo").param("k1", "v1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +138,8 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i -X POST"
|
"$ curl 'http://localhost/foo' -i -X POST"
|
||||||
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
|
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
|
||||||
documentCurlRequest("post-request-with-multiple-parameters", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"post-request-with-multiple-parameters",
|
||||||
result(post("/foo").param("k1", "v1", "v1-bis").param("k2", "v2")));
|
result(post("/foo").param("k1", "v1", "v1-bis").param("k2", "v2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +150,8 @@ public class CurlDocumentationTests {
|
|||||||
.withContents(
|
.withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
|
"$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'"));
|
||||||
documentCurlRequest("post-request-with-url-encoded-parameter", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"post-request-with-url-encoded-parameter",
|
||||||
result(post("/foo").param("k1", "a&b")));
|
result(post("/foo").param("k1", "a&b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +160,7 @@ public class CurlDocumentationTests {
|
|||||||
this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents(
|
this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'"));
|
"$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'"));
|
||||||
documentCurlRequest("put-request-with-one-parameter", null).handle(
|
new CurlRequestSnippet().document("put-request-with-one-parameter",
|
||||||
result(put("/foo").param("k1", "v1")));
|
result(put("/foo").param("k1", "v1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +171,8 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i -X PUT"
|
"$ curl 'http://localhost/foo' -i -X PUT"
|
||||||
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
|
+ " -d 'k1=v1&k1=v1-bis&k2=v2'"));
|
||||||
documentCurlRequest("put-request-with-multiple-parameters", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"put-request-with-multiple-parameters",
|
||||||
result(put("/foo").param("k1", "v1", "v1-bis").param("k2", "v2")));
|
result(put("/foo").param("k1", "v1", "v1-bis").param("k2", "v2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +182,8 @@ public class CurlDocumentationTests {
|
|||||||
.withContents(
|
.withContents(
|
||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'"));
|
"$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'"));
|
||||||
documentCurlRequest("put-request-with-url-encoded-parameter", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"put-request-with-url-encoded-parameter",
|
||||||
result(put("/foo").param("k1", "a&b")));
|
result(put("/foo").param("k1", "a&b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +193,8 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content(
|
codeBlock("bash").content(
|
||||||
"$ curl 'http://localhost/foo' -i"
|
"$ curl 'http://localhost/foo' -i"
|
||||||
+ " -H 'Content-Type: application/json' -H 'a: alpha'"));
|
+ " -H 'Content-Type: application/json' -H 'a: alpha'"));
|
||||||
documentCurlRequest("request-with-headers", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"request-with-headers",
|
||||||
result(get("/foo").contentType(MediaType.APPLICATION_JSON).header("a",
|
result(get("/foo").contentType(MediaType.APPLICATION_JSON).header("a",
|
||||||
"alpha")));
|
"alpha")));
|
||||||
}
|
}
|
||||||
@@ -198,7 +205,8 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i"));
|
codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerPort(8080);
|
request.setServerPort(8080);
|
||||||
documentCurlRequest("http-with-non-standard-port", null).handle(result(request));
|
new CurlRequestSnippet().document("http-with-non-standard-port",
|
||||||
|
result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -208,7 +216,8 @@ public class CurlDocumentationTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerPort(443);
|
request.setServerPort(443);
|
||||||
request.setScheme("https");
|
request.setScheme("https");
|
||||||
documentCurlRequest("https-with-standard-port", null).handle(result(request));
|
new CurlRequestSnippet().document("https-with-standard-port",
|
||||||
|
result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -218,7 +227,8 @@ public class CurlDocumentationTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerPort(8443);
|
request.setServerPort(8443);
|
||||||
request.setScheme("https");
|
request.setScheme("https");
|
||||||
documentCurlRequest("https-with-non-standard-port", null).handle(result(request));
|
new CurlRequestSnippet().document("https-with-non-standard-port",
|
||||||
|
result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -227,7 +237,8 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i"));
|
codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerName("api.example.com");
|
request.setServerName("api.example.com");
|
||||||
documentCurlRequest("request-with-custom-host", null).handle(result(request));
|
new CurlRequestSnippet().document("request-with-custom-host",
|
||||||
|
result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -239,8 +250,8 @@ public class CurlDocumentationTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerName("api.example.com");
|
request.setServerName("api.example.com");
|
||||||
request.setContextPath("/v3");
|
request.setContextPath("/v3");
|
||||||
documentCurlRequest("request-with-custom-context-with-slash", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
result(request));
|
"request-with-custom-context-with-slash", result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -252,8 +263,8 @@ public class CurlDocumentationTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerName("api.example.com");
|
request.setServerName("api.example.com");
|
||||||
request.setContextPath("v3");
|
request.setContextPath("v3");
|
||||||
documentCurlRequest("request-with-custom-context-without-slash", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
result(request));
|
"request-with-custom-context-without-slash", result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -265,7 +276,8 @@ public class CurlDocumentationTests {
|
|||||||
.withContents(codeBlock("bash").content(expectedContent));
|
.withContents(codeBlock("bash").content(expectedContent));
|
||||||
MockMultipartFile multipartFile = new MockMultipartFile("metadata",
|
MockMultipartFile multipartFile = new MockMultipartFile("metadata",
|
||||||
"{\"description\": \"foo\"}".getBytes());
|
"{\"description\": \"foo\"}".getBytes());
|
||||||
documentCurlRequest("multipart-post-no-original-filename", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"multipart-post-no-original-filename",
|
||||||
result(fileUpload("/upload").file(multipartFile)));
|
result(fileUpload("/upload").file(multipartFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +291,8 @@ public class CurlDocumentationTests {
|
|||||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||||
"documents/images/example.png", MediaType.IMAGE_PNG_VALUE,
|
"documents/images/example.png", MediaType.IMAGE_PNG_VALUE,
|
||||||
"bytes".getBytes());
|
"bytes".getBytes());
|
||||||
documentCurlRequest("multipart-post-with-content-type", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"multipart-post-with-content-type",
|
||||||
result(fileUpload("/upload").file(multipartFile)));
|
result(fileUpload("/upload").file(multipartFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +305,7 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content(expectedContent));
|
codeBlock("bash").content(expectedContent));
|
||||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||||
"documents/images/example.png", null, "bytes".getBytes());
|
"documents/images/example.png", null, "bytes".getBytes());
|
||||||
documentCurlRequest("multipart-post", null).handle(
|
new CurlRequestSnippet().document("multipart-post",
|
||||||
result(fileUpload("/upload").file(multipartFile)));
|
result(fileUpload("/upload").file(multipartFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +319,8 @@ public class CurlDocumentationTests {
|
|||||||
codeBlock("bash").content(expectedContent));
|
codeBlock("bash").content(expectedContent));
|
||||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||||
"documents/images/example.png", null, "bytes".getBytes());
|
"documents/images/example.png", null, "bytes".getBytes());
|
||||||
documentCurlRequest("multipart-post", null).handle(
|
new CurlRequestSnippet().document(
|
||||||
|
"multipart-post",
|
||||||
result(fileUpload("/upload").file(multipartFile)
|
result(fileUpload("/upload").file(multipartFile)
|
||||||
.param("a", "apple", "avocado").param("b", "banana")));
|
.param("a", "apple", "avocado").param("b", "banana")));
|
||||||
}
|
}
|
||||||
@@ -323,9 +337,8 @@ public class CurlDocumentationTests {
|
|||||||
"src/test/resources/custom-snippet-templates/curl-request-with-title.snippet"));
|
"src/test/resources/custom-snippet-templates/curl-request-with-title.snippet"));
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
resolver));
|
resolver));
|
||||||
documentCurlRequest("custom-attributes",
|
new CurlRequestSnippet(attributes(key("title").value(
|
||||||
attributes(key("title").value("curl request title"))).handle(
|
"curl request title"))).document("custom-attributes", result(request));
|
||||||
result(request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -19,18 +19,13 @@ package org.springframework.restdocs.http;
|
|||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
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.fileUpload;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.put;
|
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;
|
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||||
import static org.springframework.restdocs.test.SnippetMatchers.httpRequest;
|
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.restdocs.test.StubMvcResult.result;
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
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.POST;
|
||||||
@@ -44,7 +39,6 @@ import org.springframework.core.io.FileSystemResource;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.restdocs.templates.TemplateEngine;
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
@@ -52,12 +46,13 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
|||||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link HttpDocumentation}
|
* Tests for {@link HttpRequestSnippet}
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Jonathan Pearlin
|
* @author Jonathan Pearlin
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class HttpDocumentationTests {
|
public class HttpRequestDocumentationHandlerTests {
|
||||||
|
|
||||||
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||||
|
|
||||||
@@ -70,8 +65,8 @@ public class HttpDocumentationTests {
|
|||||||
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header(
|
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header(
|
||||||
"Alpha", "a"));
|
"Alpha", "a"));
|
||||||
|
|
||||||
documentHttpRequest("get-request", null).handle(
|
new HttpRequestSnippet().document("get-request", result(get("/foo")
|
||||||
result(get("/foo").header("Alpha", "a")));
|
.header("Alpha", "a")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -79,7 +74,7 @@ public class HttpDocumentationTests {
|
|||||||
this.snippet.expectHttpRequest("get-request-with-query-string").withContents(
|
this.snippet.expectHttpRequest("get-request-with-query-string").withContents(
|
||||||
httpRequest(GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost"));
|
httpRequest(GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost"));
|
||||||
|
|
||||||
documentHttpRequest("get-request-with-query-string", null).handle(
|
new HttpRequestSnippet().document("get-request-with-query-string",
|
||||||
result(get("/foo?bar=baz")));
|
result(get("/foo?bar=baz")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +83,7 @@ public class HttpDocumentationTests {
|
|||||||
this.snippet.expectHttpRequest("get-request-with-parameter").withContents(
|
this.snippet.expectHttpRequest("get-request-with-parameter").withContents(
|
||||||
httpRequest(GET, "/foo?b%26r=baz").header(HttpHeaders.HOST, "localhost"));
|
httpRequest(GET, "/foo?b%26r=baz").header(HttpHeaders.HOST, "localhost"));
|
||||||
|
|
||||||
documentHttpRequest("get-request-with-parameter", null).handle(
|
new HttpRequestSnippet().document("get-request-with-parameter",
|
||||||
result(get("/foo").param("b&r", "baz")));
|
result(get("/foo").param("b&r", "baz")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +93,7 @@ public class HttpDocumentationTests {
|
|||||||
httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content(
|
httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content(
|
||||||
"Hello, world"));
|
"Hello, world"));
|
||||||
|
|
||||||
documentHttpRequest("post-request-with-content", null).handle(
|
new HttpRequestSnippet().document("post-request-with-content",
|
||||||
result(post("/foo").content("Hello, world")));
|
result(post("/foo").content("Hello, world")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +104,7 @@ public class HttpDocumentationTests {
|
|||||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
.content("b%26r=baz&a=alpha"));
|
.content("b%26r=baz&a=alpha"));
|
||||||
|
|
||||||
documentHttpRequest("post-request-with-parameter", null).handle(
|
new HttpRequestSnippet().document("post-request-with-parameter",
|
||||||
result(post("/foo").param("b&r", "baz").param("a", "alpha")));
|
result(post("/foo").param("b&r", "baz").param("a", "alpha")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +114,7 @@ public class HttpDocumentationTests {
|
|||||||
httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content(
|
httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content(
|
||||||
"Hello, world"));
|
"Hello, world"));
|
||||||
|
|
||||||
documentHttpRequest("put-request-with-content", null).handle(
|
new HttpRequestSnippet().document("put-request-with-content",
|
||||||
result(put("/foo").content("Hello, world")));
|
result(put("/foo").content("Hello, world")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,48 +125,10 @@ public class HttpDocumentationTests {
|
|||||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
.content("b%26r=baz&a=alpha"));
|
.content("b%26r=baz&a=alpha"));
|
||||||
|
|
||||||
documentHttpRequest("put-request-with-parameter", null).handle(
|
new HttpRequestSnippet().document("put-request-with-parameter",
|
||||||
result(put("/foo").param("b&r", "baz").param("a", "alpha")));
|
result(put("/foo").param("b&r", "baz").param("a", "alpha")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void basicResponse() throws IOException {
|
|
||||||
this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK));
|
|
||||||
documentHttpResponse("basic-response", null).handle(result());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void nonOkResponse() throws IOException {
|
|
||||||
this.snippet.expectHttpResponse("non-ok-response").withContents(
|
|
||||||
httpResponse(BAD_REQUEST));
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.setStatus(BAD_REQUEST.value());
|
|
||||||
documentHttpResponse("non-ok-response", null).handle(result(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void responseWithHeaders() throws IOException {
|
|
||||||
this.snippet.expectHttpResponse("response-with-headers").withContents(
|
|
||||||
httpResponse(OK) //
|
|
||||||
.header("Content-Type", "application/json") //
|
|
||||||
.header("a", "alpha"));
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
response.setHeader("a", "alpha");
|
|
||||||
documentHttpResponse("response-with-headers", null).handle(result(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void responseWithContent() throws IOException {
|
|
||||||
this.snippet.expectHttpResponse("response-with-content").withContents(
|
|
||||||
httpResponse(OK).content("content"));
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.getWriter().append("content");
|
|
||||||
documentHttpResponse("response-with-content", null).handle(result(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void multipartPost() throws IOException {
|
public void multipartPost() throws IOException {
|
||||||
String expectedContent = createPart(String.format("Content-Disposition: "
|
String expectedContent = createPart(String.format("Content-Disposition: "
|
||||||
@@ -184,7 +141,7 @@ public class HttpDocumentationTests {
|
|||||||
.content(expectedContent));
|
.content(expectedContent));
|
||||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||||
"documents/images/example.png", null, "<< data >>".getBytes());
|
"documents/images/example.png", null, "<< data >>".getBytes());
|
||||||
documentHttpRequest("multipart-post", null).handle(
|
new HttpRequestSnippet().document("multipart-post",
|
||||||
result(fileUpload("/upload").file(multipartFile)));
|
result(fileUpload("/upload").file(multipartFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +164,8 @@ public class HttpDocumentationTests {
|
|||||||
.content(expectedContent));
|
.content(expectedContent));
|
||||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||||
"documents/images/example.png", null, "<< data >>".getBytes());
|
"documents/images/example.png", null, "<< data >>".getBytes());
|
||||||
documentHttpRequest("multipart-post", null).handle(
|
new HttpRequestSnippet().document(
|
||||||
|
"multipart-post",
|
||||||
result(fileUpload("/upload").file(multipartFile)
|
result(fileUpload("/upload").file(multipartFile)
|
||||||
.param("a", "apple", "avocado").param("b", "banana")));
|
.param("a", "apple", "avocado").param("b", "banana")));
|
||||||
}
|
}
|
||||||
@@ -226,7 +184,8 @@ public class HttpDocumentationTests {
|
|||||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||||
"documents/images/example.png", MediaType.IMAGE_PNG_VALUE,
|
"documents/images/example.png", MediaType.IMAGE_PNG_VALUE,
|
||||||
"<< data >>".getBytes());
|
"<< data >>".getBytes());
|
||||||
documentHttpRequest("multipart-post-with-content-type", null).handle(
|
new HttpRequestSnippet().document(
|
||||||
|
"multipart-post-with-content-type",
|
||||||
result(fileUpload("/upload").file(multipartFile)));
|
result(fileUpload("/upload").file(multipartFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +197,7 @@ public class HttpDocumentationTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||||
request.setServerName("api.example.com");
|
request.setServerName("api.example.com");
|
||||||
|
|
||||||
documentHttpRequest("get-request-custom-server-name", null).handle(
|
new HttpRequestSnippet().document("get-request-custom-server-name",
|
||||||
result(request));
|
result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +206,7 @@ public class HttpDocumentationTests {
|
|||||||
this.snippet.expectHttpRequest("get-request-custom-host").withContents(
|
this.snippet.expectHttpRequest("get-request-custom-host").withContents(
|
||||||
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com"));
|
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com"));
|
||||||
|
|
||||||
documentHttpRequest("get-request-custom-host", null).handle(
|
new HttpRequestSnippet().document("get-request-custom-host",
|
||||||
result(get("/foo").header(HttpHeaders.HOST, "api.example.com")));
|
result(get("/foo").header(HttpHeaders.HOST, "api.example.com")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,25 +222,8 @@ public class HttpDocumentationTests {
|
|||||||
"src/test/resources/custom-snippet-templates/http-request-with-title.snippet"));
|
"src/test/resources/custom-snippet-templates/http-request-with-title.snippet"));
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
resolver));
|
resolver));
|
||||||
documentHttpRequest("request-with-snippet-attributes",
|
new HttpRequestSnippet(attributes(key("title").value(
|
||||||
attributes(key("title").value("Title for the request"))).handle(
|
"Title for the request"))).document("request-with-snippet-attributes",
|
||||||
result(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void responseWithCustomSnippetAttributes() throws IOException {
|
|
||||||
this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents(
|
|
||||||
containsString("Title for the response"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
|
||||||
when(resolver.resolveTemplateResource("http-response"))
|
|
||||||
.thenReturn(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/http-response-with-title.snippet"));
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
documentHttpResponse("response-with-snippet-attributes",
|
|
||||||
attributes(key("title").value("Title for the response"))).handle(
|
|
||||||
result(request));
|
result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,12 +232,12 @@ public class HttpDocumentationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String createPart(String content, boolean last) {
|
private String createPart(String content, boolean last) {
|
||||||
String boundary = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
|
||||||
StringBuilder part = new StringBuilder();
|
StringBuilder part = new StringBuilder();
|
||||||
part.append(String.format("--%s%n%s%n", boundary, content));
|
part.append(String.format("--%s%n%s%n", BOUNDARY, content));
|
||||||
if (last) {
|
if (last) {
|
||||||
part.append(String.format("--%s--", boundary));
|
part.append(String.format("--%s--", BOUNDARY));
|
||||||
}
|
}
|
||||||
return part.toString();
|
return part.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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.http;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
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.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.StubMvcResult.result;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
|
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||||
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link HttpResponseSnippet}
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @author Jonathan Pearlin
|
||||||
|
*/
|
||||||
|
public class HttpResponseDocumentationHandlerTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void basicResponse() throws IOException {
|
||||||
|
this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK));
|
||||||
|
new HttpResponseSnippet().document("basic-response", result());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonOkResponse() throws IOException {
|
||||||
|
this.snippet.expectHttpResponse("non-ok-response").withContents(
|
||||||
|
httpResponse(BAD_REQUEST));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.setStatus(BAD_REQUEST.value());
|
||||||
|
new HttpResponseSnippet().document("non-ok-response",
|
||||||
|
result(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseWithHeaders() throws IOException {
|
||||||
|
this.snippet.expectHttpResponse("response-with-headers").withContents(
|
||||||
|
httpResponse(OK) //
|
||||||
|
.header("Content-Type", "application/json") //
|
||||||
|
.header("a", "alpha"));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
response.setHeader("a", "alpha");
|
||||||
|
new HttpResponseSnippet().document("response-with-headers",
|
||||||
|
result(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseWithContent() throws IOException {
|
||||||
|
this.snippet.expectHttpResponse("response-with-content").withContents(
|
||||||
|
httpResponse(OK).content("content"));
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.getWriter().append("content");
|
||||||
|
new HttpResponseSnippet().document("response-with-content",
|
||||||
|
result(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseWithCustomSnippetAttributes() throws IOException {
|
||||||
|
this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents(
|
||||||
|
containsString("Title for the response"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||||
|
when(resolver.resolveTemplateResource("http-response"))
|
||||||
|
.thenReturn(
|
||||||
|
new FileSystemResource(
|
||||||
|
"src/test/resources/custom-snippet-templates/http-response-with-title.snippet"));
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
new HttpResponseSnippet(attributes(key("title").value(
|
||||||
|
"Title for the response"))).document("response-with-snippet-attributes",
|
||||||
|
result(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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.hypermedia;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ContentTypeLinkExtractor}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public class ContentTypeLinkExtractorTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractionFailsWithNullContentType() throws IOException {
|
||||||
|
this.thrown.expect(IllegalStateException.class);
|
||||||
|
new ContentTypeLinkExtractor().extractLinks(this.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractorCalledWithMatchingContextType() throws IOException {
|
||||||
|
Map<MediaType, LinkExtractor> extractors = new HashMap<>();
|
||||||
|
LinkExtractor extractor = mock(LinkExtractor.class);
|
||||||
|
extractors.put(MediaType.APPLICATION_JSON, extractor);
|
||||||
|
this.response.setContentType("application/json");
|
||||||
|
new ContentTypeLinkExtractor(extractors).extractLinks(this.response);
|
||||||
|
verify(extractor).extractLinks(this.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractorCalledWithCompatibleContextType() throws IOException {
|
||||||
|
Map<MediaType, LinkExtractor> extractors = new HashMap<>();
|
||||||
|
LinkExtractor extractor = mock(LinkExtractor.class);
|
||||||
|
extractors.put(MediaType.APPLICATION_JSON, extractor);
|
||||||
|
this.response.setContentType("application/json;foo=bar");
|
||||||
|
new ContentTypeLinkExtractor(extractors).extractLinks(this.response);
|
||||||
|
verify(extractor).extractLinks(this.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -37,7 +37,8 @@ import org.springframework.util.LinkedMultiValueMap;
|
|||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameterized tests for {@link LinkExtractors} with various payloads.
|
* Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with
|
||||||
|
* various payloads.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
@@ -50,8 +51,8 @@ public class LinkExtractorsPayloadTests {
|
|||||||
|
|
||||||
@Parameters
|
@Parameters
|
||||||
public static Collection<Object[]> data() {
|
public static Collection<Object[]> data() {
|
||||||
return Arrays.asList(new Object[] { LinkExtractors.halLinks(), "hal" },
|
return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" },
|
||||||
new Object[] { LinkExtractors.atomLinks(), "atom" });
|
new Object[] { new AtomLinkExtractor(), "atom" });
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) {
|
public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) {
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.hypermedia;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.restdocs.hypermedia.LinkExtractors.AtomLinkExtractor;
|
|
||||||
import org.springframework.restdocs.hypermedia.LinkExtractors.HalLinkExtractor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link LinkExtractors}.
|
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
|
||||||
public class LinkExtractorsTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void nullContentTypeYieldsNullExtractor() {
|
|
||||||
assertThat(LinkExtractors.extractorForContentType(null), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void emptyContentTypeYieldsNullExtractor() {
|
|
||||||
assertThat(LinkExtractors.extractorForContentType(""), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void applicationJsonContentTypeYieldsAtomExtractor() {
|
|
||||||
LinkExtractor linkExtractor = LinkExtractors
|
|
||||||
.extractorForContentType("application/json");
|
|
||||||
assertThat(linkExtractor, instanceOf(AtomLinkExtractor.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void applicationHalJsonContentTypeYieldsHalExtractor() {
|
|
||||||
LinkExtractor linkExtractor = LinkExtractors
|
|
||||||
.extractorForContentType("application/hal+json");
|
|
||||||
assertThat(linkExtractor, instanceOf(HalLinkExtractor.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void contentTypeWithParameterYieldsExtractor() {
|
|
||||||
LinkExtractor linkExtractor = LinkExtractors
|
|
||||||
.extractorForContentType("application/json;foo=bar");
|
|
||||||
assertThat(linkExtractor, instanceOf(AtomLinkExtractor.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,13 +20,14 @@ import static org.hamcrest.CoreMatchers.equalTo;
|
|||||||
import static org.hamcrest.CoreMatchers.startsWith;
|
import static org.hamcrest.CoreMatchers.startsWith;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks;
|
|
||||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||||
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
|
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
|
||||||
import static org.springframework.restdocs.test.StubMvcResult.result;
|
import static org.springframework.restdocs.test.StubMvcResult.result;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -34,7 +35,7 @@ import org.junit.rules.ExpectedException;
|
|||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
import org.springframework.restdocs.templates.TemplateEngine;
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||||
@@ -43,11 +44,11 @@ import org.springframework.util.LinkedMultiValueMap;
|
|||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link HypermediaDocumentation}
|
* Tests for {@link LinksSnippet}
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
public class HypermediaDocumentationTests {
|
public class LinksDocumentationHandlerTests {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedSnippet snippet = new ExpectedSnippet();
|
public ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
@@ -57,21 +58,22 @@ public class HypermediaDocumentationTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void undocumentedLink() throws IOException {
|
public void undocumentedLink() throws IOException {
|
||||||
this.thrown.expect(SnippetGenerationException.class);
|
this.thrown.expect(SnippetException.class);
|
||||||
this.thrown.expectMessage(equalTo("Links with the following relations were not"
|
this.thrown.expectMessage(equalTo("Links with the following relations were not"
|
||||||
+ " documented: [foo]"));
|
+ " documented: [foo]"));
|
||||||
documentLinks("undocumented-link", null,
|
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo",
|
||||||
new StubLinkExtractor().withLinks(new Link("foo", "bar"))).handle(
|
"bar")), Collections.<LinkDescriptor> emptyList()).document(
|
||||||
result());
|
"undocumented-link", result());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void missingLink() throws IOException {
|
public void missingLink() throws IOException {
|
||||||
this.thrown.expect(SnippetGenerationException.class);
|
this.thrown.expect(SnippetException.class);
|
||||||
this.thrown.expectMessage(equalTo("Links with the following relations were not"
|
this.thrown.expectMessage(equalTo("Links with the following relations were not"
|
||||||
+ " found in the response: [foo]"));
|
+ " found in the response: [foo]"));
|
||||||
documentLinks("missing-link", null, new StubLinkExtractor(),
|
new LinksSnippet(new StubLinkExtractor(),
|
||||||
new LinkDescriptor("foo").description("bar")).handle(result());
|
Arrays.asList(new LinkDescriptor("foo").description("bar"))).document(
|
||||||
|
"missing-link", result());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -79,9 +81,9 @@ public class HypermediaDocumentationTests {
|
|||||||
this.snippet.expectLinks("documented-optional-link").withContents( //
|
this.snippet.expectLinks("documented-optional-link").withContents( //
|
||||||
tableWithHeader("Relation", "Description") //
|
tableWithHeader("Relation", "Description") //
|
||||||
.row("foo", "bar"));
|
.row("foo", "bar"));
|
||||||
documentLinks("documented-optional-link", null,
|
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo",
|
||||||
new StubLinkExtractor().withLinks(new Link("foo", "blah")),
|
"blah")), Arrays.asList(new LinkDescriptor("foo").description("bar")
|
||||||
new LinkDescriptor("foo").description("bar").optional()).handle(result());
|
.optional())).document("documented-optional-link", result());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -89,19 +91,20 @@ public class HypermediaDocumentationTests {
|
|||||||
this.snippet.expectLinks("missing-optional-link").withContents( //
|
this.snippet.expectLinks("missing-optional-link").withContents( //
|
||||||
tableWithHeader("Relation", "Description") //
|
tableWithHeader("Relation", "Description") //
|
||||||
.row("foo", "bar"));
|
.row("foo", "bar"));
|
||||||
documentLinks("missing-optional-link", null, new StubLinkExtractor(),
|
new LinksSnippet(new StubLinkExtractor(),
|
||||||
new LinkDescriptor("foo").description("bar").optional()).handle(result());
|
Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
|
||||||
|
.document("missing-optional-link", result());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void undocumentedLinkAndMissingLink() throws IOException {
|
public void undocumentedLinkAndMissingLink() throws IOException {
|
||||||
this.thrown.expect(SnippetGenerationException.class);
|
this.thrown.expect(SnippetException.class);
|
||||||
this.thrown.expectMessage(equalTo("Links with the following relations were not"
|
this.thrown.expectMessage(equalTo("Links with the following relations were not"
|
||||||
+ " documented: [a]. Links with the following relations were not"
|
+ " documented: [a]. Links with the following relations were not"
|
||||||
+ " found in the response: [foo]"));
|
+ " found in the response: [foo]"));
|
||||||
documentLinks("undocumented-link-and-missing-link", null,
|
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||||
new StubLinkExtractor().withLinks(new Link("a", "alpha")),
|
"alpha")), Arrays.asList(new LinkDescriptor("foo").description("bar")))
|
||||||
new LinkDescriptor("foo").description("bar")).handle(result());
|
.document("undocumented-link-and-missing-link", result());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -110,12 +113,11 @@ public class HypermediaDocumentationTests {
|
|||||||
tableWithHeader("Relation", "Description") //
|
tableWithHeader("Relation", "Description") //
|
||||||
.row("a", "one") //
|
.row("a", "one") //
|
||||||
.row("b", "two"));
|
.row("b", "two"));
|
||||||
documentLinks(
|
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||||
"documented-links",
|
"alpha"), new Link("b", "bravo")), Arrays.asList(
|
||||||
null,
|
new LinkDescriptor("a").description("one"),
|
||||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
|
new LinkDescriptor("b").description("two"))).document("documented-links",
|
||||||
"bravo")), new LinkDescriptor("a").description("one"),
|
result());
|
||||||
new LinkDescriptor("b").description("two")).handle(result());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -132,20 +134,18 @@ public class HypermediaDocumentationTests {
|
|||||||
"src/test/resources/custom-snippet-templates/links-with-extra-column.snippet"));
|
"src/test/resources/custom-snippet-templates/links-with-extra-column.snippet"));
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
resolver));
|
resolver));
|
||||||
documentLinks(
|
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||||
"links-with-custom-descriptor-attributes",
|
"alpha"), new Link("b", "bravo")), Arrays.asList(
|
||||||
null,
|
|
||||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
|
|
||||||
"bravo")),
|
|
||||||
new LinkDescriptor("a").description("one").attributes(
|
new LinkDescriptor("a").description("one").attributes(
|
||||||
key("foo").value("alpha")),
|
key("foo").value("alpha")),
|
||||||
new LinkDescriptor("b").description("two").attributes(
|
new LinkDescriptor("b").description("two").attributes(
|
||||||
key("foo").value("bravo"))).handle(result(request));
|
key("foo").value("bravo")))).document(
|
||||||
|
"links-with-custom-descriptor-attributes", result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void linksWithCustomAttribute() throws IOException {
|
public void linksWithCustomAttributes() throws IOException {
|
||||||
this.snippet.expectLinks("links-with-custom-attribute").withContents(
|
this.snippet.expectLinks("links-with-custom-attributes").withContents(
|
||||||
startsWith(".Title for the links"));
|
startsWith(".Title for the links"));
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||||
@@ -155,12 +155,12 @@ public class HypermediaDocumentationTests {
|
|||||||
"src/test/resources/custom-snippet-templates/links-with-title.snippet"));
|
"src/test/resources/custom-snippet-templates/links-with-title.snippet"));
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
resolver));
|
resolver));
|
||||||
documentLinks(
|
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||||
"links-with-custom-attribute",
|
"alpha"), new Link("b", "bravo")), attributes(key("title").value(
|
||||||
attributes(key("title").value("Title for the links")),
|
"Title for the links")), Arrays.asList(
|
||||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
|
new LinkDescriptor("a").description("one"),
|
||||||
"bravo")), new LinkDescriptor("a").description("one"),
|
new LinkDescriptor("b").description("two"))).document(
|
||||||
new LinkDescriptor("b").description("two")).handle(result(request));
|
"links-with-custom-attributes", result(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StubLinkExtractor implements LinkExtractor {
|
private static class StubLinkExtractor implements LinkExtractor {
|
||||||
@@ -24,7 +24,7 @@ import java.util.Arrays;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link FieldValidator}
|
* Tests for {@link FieldValidator}
|
||||||
@@ -73,7 +73,7 @@ public class FieldValidatorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void missingField() throws IOException {
|
public void missingField() throws IOException {
|
||||||
this.thrownException.expect(SnippetGenerationException.class);
|
this.thrownException.expect(SnippetException.class);
|
||||||
this.thrownException
|
this.thrownException
|
||||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||||
+ " in the payload: [y, z]"));
|
+ " in the payload: [y, z]"));
|
||||||
@@ -84,7 +84,7 @@ public class FieldValidatorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void undocumentedField() throws IOException {
|
public void undocumentedField() throws IOException {
|
||||||
this.thrownException.expect(SnippetGenerationException.class);
|
this.thrownException.expect(SnippetException.class);
|
||||||
this.thrownException.expectMessage(equalTo(String
|
this.thrownException.expectMessage(equalTo(String
|
||||||
.format("The following parts of the payload were not"
|
.format("The following parts of the payload were not"
|
||||||
+ " documented:%n{%n \"a\" : {%n \"c\" : true%n }%n}")));
|
+ " documented:%n{%n \"a\" : {%n \"c\" : true%n }%n}")));
|
||||||
|
|||||||
@@ -1,282 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.payload;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.endsWith;
|
|
||||||
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;
|
|
||||||
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 java.io.IOException;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.springframework.core.io.FileSystemResource;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
|
||||||
import org.springframework.restdocs.templates.TemplateEngine;
|
|
||||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
|
||||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
|
||||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link PayloadDocumentation}
|
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
|
||||||
public class PayloadDocumentationTests {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final ExpectedException thrown = ExpectedException.none();
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final ExpectedSnippet snippet = new ExpectedSnippet();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void mapRequestWithFields() throws IOException {
|
|
||||||
this.snippet.expectRequestFields("map-request-with-fields").withContents( //
|
|
||||||
tableWithHeader("Path", "Type", "Description") //
|
|
||||||
.row("a.b", "Number", "one") //
|
|
||||||
.row("a.c", "String", "two") //
|
|
||||||
.row("a", "Object", "three"));
|
|
||||||
|
|
||||||
documentRequestFields("map-request-with-fields", null,
|
|
||||||
fieldWithPath("a.b").description("one"),
|
|
||||||
fieldWithPath("a.c").description("two"),
|
|
||||||
fieldWithPath("a").description("three")).handle(
|
|
||||||
result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void arrayRequestWithFields() throws IOException {
|
|
||||||
this.snippet.expectRequestFields("array-request-with-fields").withContents( //
|
|
||||||
tableWithHeader("Path", "Type", "Description") //
|
|
||||||
.row("[]a.b", "Number", "one") //
|
|
||||||
.row("[]a.c", "String", "two") //
|
|
||||||
.row("[]a", "Object", "three"));
|
|
||||||
|
|
||||||
documentRequestFields("array-request-with-fields", null,
|
|
||||||
fieldWithPath("[]a.b").description("one"),
|
|
||||||
fieldWithPath("[]a.c").description("two"),
|
|
||||||
fieldWithPath("[]a").description("three")).handle(
|
|
||||||
result(get("/foo").content(
|
|
||||||
"[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void mapResponseWithFields() throws IOException {
|
|
||||||
this.snippet.expectResponseFields("map-response-with-fields").withContents(//
|
|
||||||
tableWithHeader("Path", "Type", "Description") //
|
|
||||||
.row("id", "Number", "one") //
|
|
||||||
.row("date", "String", "two") //
|
|
||||||
.row("assets", "Array", "three") //
|
|
||||||
.row("assets[]", "Object", "four") //
|
|
||||||
.row("assets[].id", "Number", "five") //
|
|
||||||
.row("assets[].name", "String", "six"));
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.getWriter().append(
|
|
||||||
"{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":"
|
|
||||||
+ " [{\"id\":356,\"name\": \"sample\"}]}");
|
|
||||||
documentResponseFields("map-response-with-fields", null,
|
|
||||||
fieldWithPath("id").description("one"),
|
|
||||||
fieldWithPath("date").description("two"),
|
|
||||||
fieldWithPath("assets").description("three"),
|
|
||||||
fieldWithPath("assets[]").description("four"),
|
|
||||||
fieldWithPath("assets[].id").description("five"),
|
|
||||||
fieldWithPath("assets[].name").description("six")).handle(
|
|
||||||
result(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void arrayResponseWithFields() throws IOException {
|
|
||||||
this.snippet.expectResponseFields("array-response-with-fields").withContents( //
|
|
||||||
tableWithHeader("Path", "Type", "Description") //
|
|
||||||
.row("[]a.b", "Number", "one") //
|
|
||||||
.row("[]a.c", "String", "two") //
|
|
||||||
.row("[]a", "Object", "three"));
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.getWriter()
|
|
||||||
.append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]");
|
|
||||||
documentResponseFields("array-response-with-fields", null,
|
|
||||||
fieldWithPath("[]a.b").description("one"),
|
|
||||||
fieldWithPath("[]a.c").description("two"),
|
|
||||||
fieldWithPath("[]a").description("three")).handle(result(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void arrayResponse() throws IOException {
|
|
||||||
this.snippet.expectResponseFields("array-response").withContents( //
|
|
||||||
tableWithHeader("Path", "Type", "Description") //
|
|
||||||
.row("[]", "String", "one"));
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.getWriter().append("[\"a\", \"b\", \"c\"]");
|
|
||||||
documentResponseFields("array-response", null,
|
|
||||||
fieldWithPath("[]").description("one")).handle(result(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void undocumentedRequestField() throws IOException {
|
|
||||||
this.thrown.expect(SnippetGenerationException.class);
|
|
||||||
this.thrown
|
|
||||||
.expectMessage(startsWith("The following parts of the payload were not"
|
|
||||||
+ " documented:"));
|
|
||||||
documentRequestFields("undocumented-request-fields", null).handle(
|
|
||||||
result(get("/foo").content("{\"a\": 5}")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void missingRequestField() throws IOException {
|
|
||||||
this.thrown.expect(SnippetGenerationException.class);
|
|
||||||
this.thrown
|
|
||||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
|
||||||
+ " in the payload: [a.b]"));
|
|
||||||
documentRequestFields("missing-request-fields", null,
|
|
||||||
fieldWithPath("a.b").description("one")).handle(
|
|
||||||
result(get("/foo").content("{}")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException {
|
|
||||||
this.thrown.expect(FieldTypeRequiredException.class);
|
|
||||||
documentRequestFields("missing-optional-request-field-with-no-type", null,
|
|
||||||
fieldWithPath("a.b").description("one").optional()).handle(
|
|
||||||
result(get("/foo").content("{ }")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void undocumentedRequestFieldAndMissingRequestField() throws IOException {
|
|
||||||
this.thrown.expect(SnippetGenerationException.class);
|
|
||||||
this.thrown
|
|
||||||
.expectMessage(startsWith("The following parts of the payload were not"
|
|
||||||
+ " documented:"));
|
|
||||||
this.thrown
|
|
||||||
.expectMessage(endsWith("Fields with the following paths were not found"
|
|
||||||
+ " in the payload: [a.b]"));
|
|
||||||
documentRequestFields("undocumented-request-field-and-missing-request-field",
|
|
||||||
null, fieldWithPath("a.b").description("one")).handle(
|
|
||||||
result(get("/foo").content("{ \"a\": { \"c\": 5 }}")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void requestFieldsWithCustomDescriptorAttributes() throws IOException {
|
|
||||||
this.snippet.expectRequestFields(
|
|
||||||
"request-fields-with-custom-descriptor-attributes").withContents( //
|
|
||||||
tableWithHeader("Path", "Type", "Description", "Foo") //
|
|
||||||
.row("a.b", "Number", "one", "alpha") //
|
|
||||||
.row("a.c", "String", "two", "bravo") //
|
|
||||||
.row("a", "Object", "three", "charlie"));
|
|
||||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
|
||||||
when(resolver.resolveTemplateResource("request-fields"))
|
|
||||||
.thenReturn(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes());
|
|
||||||
documentRequestFields(
|
|
||||||
"request-fields-with-custom-descriptor-attributes",
|
|
||||||
null,
|
|
||||||
fieldWithPath("a.b").description("one").attributes(
|
|
||||||
key("foo").value("alpha")),
|
|
||||||
fieldWithPath("a.c").description("two").attributes(
|
|
||||||
key("foo").value("bravo")),
|
|
||||||
fieldWithPath("a").description("three").attributes(
|
|
||||||
key("foo").value("charlie"))).handle(result(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void responseFieldsWithCustomDescriptorAttributes() throws IOException {
|
|
||||||
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
|
|
||||||
.withContents( //
|
|
||||||
tableWithHeader("Path", "Type", "Description", "Foo") //
|
|
||||||
.row("a.b", "Number", "one", "alpha") //
|
|
||||||
.row("a.c", "String", "two", "bravo") //
|
|
||||||
.row("a", "Object", "three", "charlie"));
|
|
||||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
|
||||||
when(resolver.resolveTemplateResource("response-fields"))
|
|
||||||
.thenReturn(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.getOutputStream().print("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}");
|
|
||||||
documentResponseFields(
|
|
||||||
"response-fields-with-custom-attributes",
|
|
||||||
null,
|
|
||||||
fieldWithPath("a.b").description("one").attributes(
|
|
||||||
key("foo").value("alpha")),
|
|
||||||
fieldWithPath("a.c").description("two").attributes(
|
|
||||||
key("foo").value("bravo")),
|
|
||||||
fieldWithPath("a").description("three").attributes(
|
|
||||||
key("foo").value("charlie"))).handle(result(request, response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void requestFieldsWithCustomAttributes() throws IOException {
|
|
||||||
this.snippet.expectRequestFields("request-fields-with-custom-attributes")
|
|
||||||
.withContents(startsWith(".Custom title"));
|
|
||||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
|
||||||
when(resolver.resolveTemplateResource("request-fields"))
|
|
||||||
.thenReturn(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/request-fields-with-title.snippet"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
request.setContent("{\"a\": \"foo\"}".getBytes());
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
documentRequestFields("request-fields-with-custom-attributes",
|
|
||||||
attributes(key("title").value("Custom title")),
|
|
||||||
fieldWithPath("a").description("one")).handle(result(request, response));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void responseFieldsWithCustomAttributes() throws IOException {
|
|
||||||
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
|
|
||||||
.withContents(startsWith(".Custom title"));
|
|
||||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
|
||||||
when(resolver.resolveTemplateResource("response-fields"))
|
|
||||||
.thenReturn(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/response-fields-with-title.snippet"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
response.getOutputStream().print("{\"a\": \"foo\"}");
|
|
||||||
documentResponseFields("response-fields-with-custom-attributes",
|
|
||||||
attributes(key("title").value("Custom title")),
|
|
||||||
fieldWithPath("a").description("one")).handle(result(request, response));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* 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.payload;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.endsWith;
|
||||||
|
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.fieldWithPath;
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
|
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||||
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link RequestFieldsSnippet}
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public class RequestFieldsDocumentationHandlerTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapRequestWithFields() throws IOException {
|
||||||
|
this.snippet.expectRequestFields("map-request-with-fields").withContents( //
|
||||||
|
tableWithHeader("Path", "Type", "Description") //
|
||||||
|
.row("a.b", "Number", "one") //
|
||||||
|
.row("a.c", "String", "two") //
|
||||||
|
.row("a", "Object", "three"));
|
||||||
|
|
||||||
|
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||||
|
.description("one"), fieldWithPath("a.c").description("two"),
|
||||||
|
fieldWithPath("a").description("three"))).document(
|
||||||
|
"map-request-with-fields",
|
||||||
|
result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void arrayRequestWithFields() throws IOException {
|
||||||
|
this.snippet.expectRequestFields("array-request-with-fields").withContents( //
|
||||||
|
tableWithHeader("Path", "Type", "Description") //
|
||||||
|
.row("[]a.b", "Number", "one") //
|
||||||
|
.row("[]a.c", "String", "two") //
|
||||||
|
.row("[]a", "Object", "three"));
|
||||||
|
|
||||||
|
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b")
|
||||||
|
.description("one"), fieldWithPath("[]a.c").description("two"),
|
||||||
|
fieldWithPath("[]a").description("three"))).document(
|
||||||
|
"array-request-with-fields",
|
||||||
|
result(get("/foo").content(
|
||||||
|
"[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void undocumentedRequestField() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown
|
||||||
|
.expectMessage(startsWith("The following parts of the payload were not"
|
||||||
|
+ " documented:"));
|
||||||
|
new RequestFieldsSnippet(Collections.<FieldDescriptor> emptyList())
|
||||||
|
.document("undocumented-request-field",
|
||||||
|
result(get("/foo").content("{\"a\": 5}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void missingRequestField() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown
|
||||||
|
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||||
|
+ " in the payload: [a.b]"));
|
||||||
|
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||||
|
.description("one"))).document("missing-request-fields",
|
||||||
|
result(get("/foo").content("{}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException {
|
||||||
|
this.thrown.expect(FieldTypeRequiredException.class);
|
||||||
|
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||||
|
.description("one").optional())).document(
|
||||||
|
"missing-optional-request-field-with-no-type", result(get("/foo")
|
||||||
|
.content("{ }")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void undocumentedRequestFieldAndMissingRequestField() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown
|
||||||
|
.expectMessage(startsWith("The following parts of the payload were not"
|
||||||
|
+ " documented:"));
|
||||||
|
this.thrown
|
||||||
|
.expectMessage(endsWith("Fields with the following paths were not found"
|
||||||
|
+ " in the payload: [a.b]"));
|
||||||
|
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||||
|
.description("one"))).document(
|
||||||
|
"undocumented-request-field-and-missing-request-field",
|
||||||
|
result(get("/foo").content("{ \"a\": { \"c\": 5 }}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestFieldsWithCustomDescriptorAttributes() throws IOException {
|
||||||
|
this.snippet.expectRequestFields(
|
||||||
|
"request-fields-with-custom-descriptor-attributes").withContents( //
|
||||||
|
tableWithHeader("Path", "Type", "Description", "Foo") //
|
||||||
|
.row("a.b", "Number", "one", "alpha") //
|
||||||
|
.row("a.c", "String", "two", "bravo") //
|
||||||
|
.row("a", "Object", "three", "charlie"));
|
||||||
|
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||||
|
when(resolver.resolveTemplateResource("request-fields")).thenReturn(
|
||||||
|
snippetResource("request-fields-with-extra-column"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes());
|
||||||
|
new RequestFieldsSnippet(Arrays.asList(
|
||||||
|
fieldWithPath("a.b").description("one").attributes(
|
||||||
|
key("foo").value("alpha")),
|
||||||
|
fieldWithPath("a.c").description("two").attributes(
|
||||||
|
key("foo").value("bravo")),
|
||||||
|
fieldWithPath("a").description("three").attributes(
|
||||||
|
key("foo").value("charlie")))).document(
|
||||||
|
"request-fields-with-custom-descriptor-attributes", result(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestFieldsWithCustomAttributes() throws IOException {
|
||||||
|
this.snippet.expectRequestFields("request-fields-with-custom-attributes")
|
||||||
|
.withContents(startsWith(".Custom title"));
|
||||||
|
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||||
|
when(resolver.resolveTemplateResource("request-fields")).thenReturn(
|
||||||
|
snippetResource("request-fields-with-title"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
request.setContent("{\"a\": \"foo\"}".getBytes());
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
new RequestFieldsSnippet(attributes(key("title").value(
|
||||||
|
"Custom title")), Arrays.asList(fieldWithPath("a").description("one")))
|
||||||
|
.document("request-fields-with-custom-attributes",
|
||||||
|
result(request, response));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileSystemResource snippetResource(String name) {
|
||||||
|
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
|
||||||
|
+ name + ".snippet");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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.payload;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.startsWith;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
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.restdocs.test.SnippetMatchers.tableWithHeader;
|
||||||
|
import static org.springframework.restdocs.test.StubMvcResult.result;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
|
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||||
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PayloadDocumentation}
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public class ResponseFieldsDocumentationHandlerTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapResponseWithFields() throws IOException {
|
||||||
|
this.snippet.expectResponseFields("map-response-with-fields").withContents(//
|
||||||
|
tableWithHeader("Path", "Type", "Description") //
|
||||||
|
.row("id", "Number", "one") //
|
||||||
|
.row("date", "String", "two") //
|
||||||
|
.row("assets", "Array", "three") //
|
||||||
|
.row("assets[]", "Object", "four") //
|
||||||
|
.row("assets[].id", "Number", "five") //
|
||||||
|
.row("assets[].name", "String", "six"));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.getWriter().append(
|
||||||
|
"{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":"
|
||||||
|
+ " [{\"id\":356,\"name\": \"sample\"}]}");
|
||||||
|
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id")
|
||||||
|
.description("one"), fieldWithPath("date").description("two"),
|
||||||
|
fieldWithPath("assets").description("three"), fieldWithPath("assets[]")
|
||||||
|
.description("four"),
|
||||||
|
fieldWithPath("assets[].id").description("five"),
|
||||||
|
fieldWithPath("assets[].name").description("six"))).document(
|
||||||
|
"map-response-with-fields", result(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void arrayResponseWithFields() throws IOException {
|
||||||
|
this.snippet.expectResponseFields("array-response-with-fields").withContents( //
|
||||||
|
tableWithHeader("Path", "Type", "Description") //
|
||||||
|
.row("[]a.b", "Number", "one") //
|
||||||
|
.row("[]a.c", "String", "two") //
|
||||||
|
.row("[]a", "Object", "three"));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.getWriter()
|
||||||
|
.append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]");
|
||||||
|
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b")
|
||||||
|
.description("one"), fieldWithPath("[]a.c").description("two"),
|
||||||
|
fieldWithPath("[]a").description("three"))).document(
|
||||||
|
"array-response-with-fields", result(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void arrayResponse() throws IOException {
|
||||||
|
this.snippet.expectResponseFields("array-response").withContents( //
|
||||||
|
tableWithHeader("Path", "Type", "Description") //
|
||||||
|
.row("[]", "String", "one"));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.getWriter().append("[\"a\", \"b\", \"c\"]");
|
||||||
|
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]")
|
||||||
|
.description("one"))).document("array-response", result(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseFieldsWithCustomDescriptorAttributes() throws IOException {
|
||||||
|
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
|
||||||
|
.withContents( //
|
||||||
|
tableWithHeader("Path", "Type", "Description", "Foo") //
|
||||||
|
.row("a.b", "Number", "one", "alpha") //
|
||||||
|
.row("a.c", "String", "two", "bravo") //
|
||||||
|
.row("a", "Object", "three", "charlie"));
|
||||||
|
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||||
|
when(resolver.resolveTemplateResource("response-fields")).thenReturn(
|
||||||
|
snippetResource("response-fields-with-extra-column"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.getOutputStream().print("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}");
|
||||||
|
new ResponseFieldsSnippet(Arrays.asList(
|
||||||
|
fieldWithPath("a.b").description("one").attributes(
|
||||||
|
key("foo").value("alpha")),
|
||||||
|
fieldWithPath("a.c").description("two").attributes(
|
||||||
|
key("foo").value("bravo")),
|
||||||
|
fieldWithPath("a").description("three").attributes(
|
||||||
|
key("foo").value("charlie")))).document(
|
||||||
|
"response-fields-with-custom-attributes", result(request, response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseFieldsWithCustomAttributes() throws IOException {
|
||||||
|
this.snippet.expectResponseFields("response-fields-with-custom-attributes")
|
||||||
|
.withContents(startsWith(".Custom title"));
|
||||||
|
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||||
|
when(resolver.resolveTemplateResource("response-fields")).thenReturn(
|
||||||
|
snippetResource("response-fields-with-title"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
response.getOutputStream().print("{\"a\": \"foo\"}");
|
||||||
|
new ResponseFieldsSnippet(attributes(key("title").value(
|
||||||
|
"Custom title")), Arrays.asList(fieldWithPath("a").description("one")))
|
||||||
|
.document("response-fields-with-custom-attributes",
|
||||||
|
result(request, response));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileSystemResource snippetResource(String name) {
|
||||||
|
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
|
||||||
|
+ name + ".snippet");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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 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.parameterWithName;
|
||||||
|
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.restdocs.test.TestRequestBuilders.get;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.restdocs.config.RestDocumentationContext;
|
||||||
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
|
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||||
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PathParametersSnippet}
|
||||||
|
*
|
||||||
|
* @author awilkinson
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class PathParametersDocumentationHandlerTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void undocumentedPathParameter() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown.expectMessage(equalTo("Path parameters with the following names were"
|
||||||
|
+ " not documented: [a]"));
|
||||||
|
new PathParametersSnippet(Collections.<ParameterDescriptor> emptyList())
|
||||||
|
.document("undocumented-path-parameter", result(get("/{a}/", "alpha")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void missingPathParameter() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown.expectMessage(equalTo("Path parameters with the following names were"
|
||||||
|
+ " not found in the request: [a]"));
|
||||||
|
new PathParametersSnippet(
|
||||||
|
Arrays.asList(parameterWithName("a").description("one"))).document(
|
||||||
|
"missing-path-parameter", result(get("/")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void undocumentedAndMissingPathParameters() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.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]"));
|
||||||
|
new PathParametersSnippet(
|
||||||
|
Arrays.asList(parameterWithName("a").description("one"))).document(
|
||||||
|
"undocumented-and-missing-path-parameters", 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"));
|
||||||
|
new PathParametersSnippet(Arrays.asList(
|
||||||
|
parameterWithName("a").description("one"), parameterWithName("b")
|
||||||
|
.description("two"))).document(
|
||||||
|
"path-parameters",
|
||||||
|
result(get("/{a}/{b}", "alpha", "banana").requestAttr(
|
||||||
|
RestDocumentationContext.class.getName(),
|
||||||
|
new RestDocumentationContext(null))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(
|
||||||
|
snippetResource("path-parameters-with-extra-column"));
|
||||||
|
new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one")
|
||||||
|
.attributes(key("foo").value("alpha")), parameterWithName("b")
|
||||||
|
.description("two").attributes(key("foo").value("bravo")))).document(
|
||||||
|
"path-parameters-with-custom-descriptor-attributes",
|
||||||
|
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(
|
||||||
|
snippetResource("path-parameters-with-title"));
|
||||||
|
new PathParametersSnippet(
|
||||||
|
attributes(key("title").value("The title")),
|
||||||
|
Arrays.asList(
|
||||||
|
parameterWithName("a").description("one").attributes(
|
||||||
|
key("foo").value("alpha")), parameterWithName("b")
|
||||||
|
.description("two").attributes(key("foo").value("bravo"))))
|
||||||
|
.document(
|
||||||
|
"path-parameters-with-custom-attributes",
|
||||||
|
result(get("{a}/{b}", "alpha", "bravo").requestAttr(
|
||||||
|
TemplateEngine.class.getName(),
|
||||||
|
new MustacheTemplateEngine(resolver))));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileSystemResource snippetResource(String name) {
|
||||||
|
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
|
||||||
|
+ name + ".snippet");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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 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.parameterWithName;
|
||||||
|
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.restdocs.test.TestRequestBuilders.get;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.restdocs.config.RestDocumentationContext;
|
||||||
|
import org.springframework.restdocs.snippet.SnippetException;
|
||||||
|
import org.springframework.restdocs.templates.TemplateEngine;
|
||||||
|
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||||
|
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||||
|
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link QueryParametersSnippet}
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public class QueryParametersDocumentationHandlerTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedSnippet snippet = new ExpectedSnippet();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void undocumentedQueryParameter() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown
|
||||||
|
.expectMessage(equalTo("Query parameters with the following names were"
|
||||||
|
+ " not documented: [a]"));
|
||||||
|
new QueryParametersSnippet(Collections.<ParameterDescriptor> emptyList())
|
||||||
|
.document("undocumented-query-parameter",
|
||||||
|
result(get("/").param("a", "alpha")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void missingQueryParameter() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.class);
|
||||||
|
this.thrown
|
||||||
|
.expectMessage(equalTo("Query parameters with the following names were"
|
||||||
|
+ " not found in the request: [a]"));
|
||||||
|
new QueryParametersSnippet(Arrays.asList(parameterWithName("a")
|
||||||
|
.description("one"))).document("missing-query-parameter",
|
||||||
|
result(get("/")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void undocumentedAndMissingQueryParameters() throws IOException {
|
||||||
|
this.thrown.expect(SnippetException.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]"));
|
||||||
|
new QueryParametersSnippet(Arrays.asList(parameterWithName("a")
|
||||||
|
.description("one"))).document(
|
||||||
|
"undocumented-and-missing-query-parameters",
|
||||||
|
result(get("/").param("b", "bravo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void queryParameterSnippetFromRequestParameters() throws IOException {
|
||||||
|
this.snippet.expectQueryParameters("query-parameter-snippet-request-parameters")
|
||||||
|
.withContents(
|
||||||
|
tableWithHeader("Parameter", "Description").row("a", "one").row(
|
||||||
|
"b", "two"));
|
||||||
|
new QueryParametersSnippet(Arrays.asList(parameterWithName("a")
|
||||||
|
.description("one"), parameterWithName("b").description("two")))
|
||||||
|
.document("query-parameter-snippet-request-parameters", result(get("/")
|
||||||
|
.param("a", "bravo").param("b", "bravo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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"));
|
||||||
|
new QueryParametersSnippet(Arrays.asList(parameterWithName("a")
|
||||||
|
.description("one"), parameterWithName("b").description("two")))
|
||||||
|
.document(
|
||||||
|
"query-parameter-snippet-request-uri-query-string",
|
||||||
|
result(get("/?a=alpha&b=bravo").requestAttr(
|
||||||
|
RestDocumentationContext.class.getName(),
|
||||||
|
new RestDocumentationContext(null))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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(
|
||||||
|
snippetResource("query-parameters-with-extra-column"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
request.addParameter("a", "bravo");
|
||||||
|
request.addParameter("b", "bravo");
|
||||||
|
new QueryParametersSnippet(Arrays.asList(
|
||||||
|
parameterWithName("a").description("one").attributes(
|
||||||
|
key("foo").value("alpha")),
|
||||||
|
parameterWithName("b").description("two").attributes(
|
||||||
|
key("foo").value("bravo")))).document(
|
||||||
|
"query-parameters-with-custom-descriptor-attributes", result(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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")).thenReturn(
|
||||||
|
snippetResource("query-parameters-with-title"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||||
|
resolver));
|
||||||
|
request.addParameter("a", "bravo");
|
||||||
|
request.addParameter("b", "bravo");
|
||||||
|
new QueryParametersSnippet(
|
||||||
|
attributes(key("title").value("The title")),
|
||||||
|
Arrays.asList(
|
||||||
|
parameterWithName("a").description("one").attributes(
|
||||||
|
key("foo").value("alpha")), parameterWithName("b")
|
||||||
|
.description("two").attributes(key("foo").value("bravo"))))
|
||||||
|
.document("query-parameters-with-custom-attributes", result(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileSystemResource snippetResource(String name) {
|
||||||
|
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
|
||||||
|
+ name + ".snippet");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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.SnippetMatchers.tableWithHeader;
|
|
||||||
import static org.springframework.restdocs.test.StubMvcResult.result;
|
|
||||||
import static org.springframework.restdocs.test.TestRequestBuilders.get;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.springframework.core.io.FileSystemResource;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.restdocs.config.RestDocumentationContext;
|
|
||||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
|
||||||
import org.springframework.restdocs.templates.TemplateEngine;
|
|
||||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
|
||||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
|
||||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests for {@link RequestDocumentation}
|
|
||||||
*
|
|
||||||
* @author Andy Wilkinson
|
|
||||||
*/
|
|
||||||
public class RequestDocumentationTests {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ExpectedSnippet snippet = new ExpectedSnippet();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
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-query-parameter", null).handle(
|
|
||||||
result(get("/").param("a", "alpha")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
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-query-parameter", null,
|
|
||||||
parameterWithName("a").description("one")).handle(result(get("/")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
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-and-missing-query-parameters", null,
|
|
||||||
parameterWithName("a").description("one")).handle(
|
|
||||||
result(get("/").param("b", "bravo")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void queryParameterSnippetFromRequestParameters() throws IOException {
|
|
||||||
this.snippet.expectQueryParameters("query-parameter-snippet-request-parameters")
|
|
||||||
.withContents(
|
|
||||||
tableWithHeader("Parameter", "Description").row("a", "one").row(
|
|
||||||
"b", "two"));
|
|
||||||
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 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(
|
|
||||||
RestDocumentationContext.class.getName(),
|
|
||||||
new RestDocumentationContext())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
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(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/query-parameters-with-extra-column.snippet"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
request.addParameter("a", "bravo");
|
|
||||||
request.addParameter("b", "bravo");
|
|
||||||
documentQueryParameters(
|
|
||||||
"query-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(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
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"))
|
|
||||||
.thenReturn(
|
|
||||||
new FileSystemResource(
|
|
||||||
"src/test/resources/custom-snippet-templates/query-parameters-with-title.snippet"));
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
|
||||||
resolver));
|
|
||||||
request.addParameter("a", "bravo");
|
|
||||||
request.addParameter("b", "bravo");
|
|
||||||
documentQueryParameters(
|
|
||||||
"query-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(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))));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.restdocs.snippet.StandardWriterResolver;
|
||||||
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
|
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ import org.hamcrest.Matcher;
|
|||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
import org.junit.runners.model.Statement;
|
import org.junit.runners.model.Statement;
|
||||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||||
import org.springframework.restdocs.test.SnippetMatchers.SnippetMatcher;
|
import org.springframework.restdocs.test.SnippetMatchers.SnippetMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code ExpectedSnippet} rule is used to verify that a
|
* The {@code ExpectedSnippet} rule is used to verify that a
|
||||||
* {@link SnippetWritingResultHandler} has generated the expected snippet.
|
* {@link TemplatedSnippet} has generated the expected snippet.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class StubMvcResult implements MvcResult {
|
|||||||
this.request.setAttribute(TemplateEngine.class.getName(),
|
this.request.setAttribute(TemplateEngine.class.getName(),
|
||||||
new MustacheTemplateEngine(new StandardTemplateResourceResolver()));
|
new MustacheTemplateEngine(new StandardTemplateResourceResolver()));
|
||||||
}
|
}
|
||||||
RestDocumentationContext context = new RestDocumentationContext();
|
RestDocumentationContext context = new RestDocumentationContext(null);
|
||||||
this.request.setAttribute(RestDocumentationContext.class.getName(), context);
|
this.request.setAttribute(RestDocumentationContext.class.getName(), context);
|
||||||
if (this.request.getAttribute(WriterResolver.class.getName()) == null) {
|
if (this.request.getAttribute(WriterResolver.class.getName()) == null) {
|
||||||
this.request.setAttribute(WriterResolver.class.getName(),
|
this.request.setAttribute(WriterResolver.class.getName(),
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ public class TestRequestBuilders {
|
|||||||
Object... urlVariables) {
|
Object... urlVariables) {
|
||||||
return org.springframework.restdocs.RestDocumentationRequestBuilders.get(
|
return org.springframework.restdocs.RestDocumentationRequestBuilders.get(
|
||||||
urlTemplate, urlVariables).requestAttr(
|
urlTemplate, urlVariables).requestAttr(
|
||||||
RestDocumentationContext.class.getName(), new RestDocumentationContext());
|
RestDocumentationContext.class.getName(),
|
||||||
|
new RestDocumentationContext(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user