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]]
|
||||
=== 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]
|
||||
----
|
||||
<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
|
||||
`org.springframework.restdocs.hypermedia.HypermediaDocumentation`.
|
||||
<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`.
|
||||
|
||||
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]
|
||||
----
|
||||
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
|
||||
`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
|
||||
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]]
|
||||
=== 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]
|
||||
----
|
||||
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
|
||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||
<3> Document a parameter named `per_page`.
|
||||
@@ -199,14 +201,15 @@ is not found in the request.
|
||||
[[documenting-your-api-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]
|
||||
----
|
||||
include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters]
|
||||
----
|
||||
<1> Build the request. Uses the static `get` method on `RestDocumentationRequestBuilders`.
|
||||
<2> Use `withPathParameters` to describe the path parameters.
|
||||
<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
|
||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||
<4> Document a parameter named `latitude`.
|
||||
@@ -244,6 +247,9 @@ documented
|
||||
| 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]]
|
||||
|
||||
@@ -200,7 +200,7 @@ be included in the project's jar:
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<configuration>
|
||||
<outputDirectory>
|
||||
${project.build.outputDirectory}/static/docs
|
||||
</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
|
||||
`org.springframework.restdocs.RestDocumentation`. `RestDocumentationConfigurer` applies
|
||||
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.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
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.test.web.servlet.MockMvc;
|
||||
@@ -29,13 +30,13 @@ public class Hypermedia {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
public void links() throws Exception {
|
||||
public void defaultExtractor() throws Exception {
|
||||
// tag::links[]
|
||||
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("index").withLinks( // <1>
|
||||
.andDo(document("index", links( // <1>
|
||||
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[]
|
||||
}
|
||||
|
||||
@@ -43,9 +44,9 @@ public class Hypermedia {
|
||||
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
//tag::explicit-extractor[]
|
||||
.andDo(document("index").withLinks(halLinks(), // <1>
|
||||
.andDo(document("index", links(halLinks(), // <1>
|
||||
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[]
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.example;
|
||||
|
||||
import static org.springframework.restdocs.RestDocumentation.document;
|
||||
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.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -27,14 +28,14 @@ public class PathParameters {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
public void pathParameters() throws Exception {
|
||||
public void pathParametersSnippet() throws Exception {
|
||||
// tag::path-parameters[]
|
||||
this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) // <1>
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("locations").withPathParameters( // <2>
|
||||
.andDo(document("locations", pathParameters( // <2>
|
||||
parameterWithName("latitude").description("The location's latitude"), // <3>
|
||||
parameterWithName("longitude").description("The location's longitude") // <4>
|
||||
));
|
||||
)));
|
||||
// end::path-parameters[]
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.example;
|
||||
|
||||
import static org.springframework.restdocs.RestDocumentation.document;
|
||||
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.key;
|
||||
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.restdocs.payload.FieldType;
|
||||
import org.springframework.restdocs.snippet.Attributes;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
public class Payload {
|
||||
@@ -37,9 +38,9 @@ private MockMvc mockMvc;
|
||||
// tag::response[]
|
||||
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("index").withResponseFields( // <1>
|
||||
.andDo(document("index", responseFields( // <1>
|
||||
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[]
|
||||
}
|
||||
|
||||
@@ -47,11 +48,11 @@ private MockMvc mockMvc;
|
||||
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
// tag::explicit-type[]
|
||||
.andDo(document("index").withResponseFields(
|
||||
.andDo(document("index", responseFields(
|
||||
fieldWithPath("contact.email")
|
||||
.type(FieldType.STRING) // <1>
|
||||
.optional()
|
||||
.description("The user's email address")));
|
||||
.description("The user's email address"))));
|
||||
// end::explicit-type[]
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ private MockMvc mockMvc;
|
||||
this.mockMvc.perform(post("/users/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
// tag::constraints[]
|
||||
.andDo(document("create-user").withRequestFields(
|
||||
.andDo(document("create-user", requestFields(
|
||||
attributes(
|
||||
key("title").value("Fields for user creation")), // <1>
|
||||
fieldWithPath("name")
|
||||
@@ -69,7 +70,7 @@ private MockMvc mockMvc;
|
||||
fieldWithPath("email")
|
||||
.description("The user's email address")
|
||||
.attributes(
|
||||
key("constraints").value("Must be a valid email address")))); // <3>
|
||||
key("constraints").value("Must be a valid email address"))))); // <3>
|
||||
// end::constraints[]
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.example;
|
||||
|
||||
import static org.springframework.restdocs.RestDocumentation.document;
|
||||
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.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -27,14 +28,14 @@ public class QueryParameters {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
public void queryParameters() throws Exception {
|
||||
public void queryParametersSnippet() throws Exception {
|
||||
// tag::query-parameters[]
|
||||
this.mockMvc.perform(get("/users?page=2&per_page=100"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("users").withQueryParameters( // <1>
|
||||
.andDo(document("users", queryParameters( // <1>
|
||||
parameterWithName("page").description("The page to retrieve"), // <2>
|
||||
parameterWithName("per_page").description("Entries per page") // <3>
|
||||
));
|
||||
)));
|
||||
// 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.document;
|
||||
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.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||
@@ -100,26 +103,26 @@ public class ApiDocumentation {
|
||||
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
||||
.andExpect(jsonPath("status", is(400)))
|
||||
.andExpect(jsonPath("path", is(notNullValue())))
|
||||
.andDo(document("error-example")
|
||||
.withResponseFields(
|
||||
.andDo(document("error-example",
|
||||
responseFields(
|
||||
fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
|
||||
fieldWithPath("message").description("A description of the cause of the error"),
|
||||
fieldWithPath("path").description("The path to which the request was made"),
|
||||
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
|
||||
public void indexExample() throws Exception {
|
||||
this.mockMvc.perform(get("/"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("index-example")
|
||||
.withLinks(
|
||||
.andDo(document("index-example",
|
||||
links(
|
||||
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>"),
|
||||
linkWithRel("profile").description("The ALPS profile for the service"))
|
||||
.withResponseFields(
|
||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources")));
|
||||
linkWithRel("profile").description("The ALPS profile for the service")),
|
||||
responseFields(
|
||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||
|
||||
}
|
||||
|
||||
@@ -135,9 +138,9 @@ public class ApiDocumentation {
|
||||
|
||||
this.mockMvc.perform(get("/notes"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("notes-list-example")
|
||||
.withResponseFields(
|
||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>")));
|
||||
.andDo(document("notes-list-example",
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -161,11 +164,11 @@ public class ApiDocumentation {
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note))).andExpect(
|
||||
status().isCreated())
|
||||
.andDo(document("notes-create-example")
|
||||
.withRequestFields(
|
||||
.andDo(document("notes-create-example",
|
||||
requestFields(
|
||||
fieldWithPath("title").description("The title 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
|
||||
@@ -198,14 +201,14 @@ public class ApiDocumentation {
|
||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())))
|
||||
.andDo(document("note-get-example")
|
||||
.withLinks(
|
||||
.andDo(document("note-get-example",
|
||||
links(
|
||||
linkWithRel("self").description("This <<resources-note,note>>"),
|
||||
linkWithRel("tags").description("This note's tags"))
|
||||
.withResponseFields(
|
||||
linkWithRel("tags").description("This note's tags")),
|
||||
responseFields(
|
||||
fieldWithPath("title").description("The title 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
|
||||
@@ -219,9 +222,9 @@ public class ApiDocumentation {
|
||||
|
||||
this.mockMvc.perform(get("/tags"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("tags-list-example")
|
||||
.withResponseFields(
|
||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>")));
|
||||
.andDo(document("tags-list-example",
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -233,9 +236,9 @@ public class ApiDocumentation {
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated())
|
||||
.andDo(document("tags-create-example")
|
||||
.withRequestFields(
|
||||
fieldWithPath("name").description("The name of the tag")));
|
||||
.andDo(document("tags-create-example",
|
||||
requestFields(
|
||||
fieldWithPath("name").description("The name of the tag"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -274,12 +277,11 @@ public class ApiDocumentation {
|
||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(noteUpdate)))
|
||||
.andExpect(status().isNoContent())
|
||||
.andDo(document("note-update-example")
|
||||
.withRequestFields(
|
||||
.andDo(document("note-update-example",
|
||||
requestFields(
|
||||
fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(),
|
||||
fieldWithPath("body").description("The body of the note").type(FieldType.STRING).optional(),
|
||||
fieldWithPath("tags").description("An array of tag resource URIs").optional()));
|
||||
|
||||
fieldWithPath("tags").description("An array of tag resource URIs").optional())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -297,13 +299,13 @@ public class ApiDocumentation {
|
||||
this.mockMvc.perform(get(tagLocation))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("name", is(tag.get("name"))))
|
||||
.andDo(document("tag-get-example")
|
||||
.withLinks(
|
||||
.andDo(document("tag-get-example",
|
||||
links(
|
||||
linkWithRel("self").description("This <<resources-tag,tag>>"),
|
||||
linkWithRel("notes").description("The <<resources-tagged-notes,notes>> that have this tag"))
|
||||
.withResponseFields(
|
||||
linkWithRel("notes").description("The <<resources-tagged-notes,notes>> that have this tag")),
|
||||
responseFields(
|
||||
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
|
||||
@@ -325,9 +327,9 @@ public class ApiDocumentation {
|
||||
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tagUpdate)))
|
||||
.andExpect(status().isNoContent())
|
||||
.andDo(document("tag-update-example")
|
||||
.withRequestFields(
|
||||
fieldWithPath("name").description("The name of the tag")));
|
||||
.andDo(document("tag-update-example",
|
||||
requestFields(
|
||||
fieldWithPath("name").description("The name of the tag"))));
|
||||
}
|
||||
|
||||
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.documentationConfiguration;
|
||||
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.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||
@@ -89,25 +92,25 @@ public class ApiDocumentation {
|
||||
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
||||
.andExpect(jsonPath("status", is(400)))
|
||||
.andExpect(jsonPath("path", is(notNullValue())))
|
||||
.andDo(document("error-example")
|
||||
.withResponseFields(
|
||||
.andDo(document("error-example",
|
||||
responseFields(
|
||||
fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
|
||||
fieldWithPath("message").description("A description of the cause of the error"),
|
||||
fieldWithPath("path").description("The path to which the request was made"),
|
||||
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
|
||||
public void indexExample() throws Exception {
|
||||
this.mockMvc.perform(get("/"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("index-example")
|
||||
.withLinks(
|
||||
.andDo(document("index-example",
|
||||
links(
|
||||
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>"))
|
||||
.withResponseFields(
|
||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources")));
|
||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>")),
|
||||
responseFields(
|
||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,9 +125,9 @@ public class ApiDocumentation {
|
||||
|
||||
this.mockMvc.perform(get("/notes"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("notes-list-example")
|
||||
.withResponseFields(
|
||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>")));
|
||||
.andDo(document("notes-list-example",
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,11 +151,11 @@ public class ApiDocumentation {
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated())
|
||||
.andDo(document("notes-create-example")
|
||||
.withRequestFields(
|
||||
.andDo(document("notes-create-example",
|
||||
requestFields(
|
||||
fieldWithPath("title").description("The title 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
|
||||
@@ -185,14 +188,14 @@ public class ApiDocumentation {
|
||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||
.andExpect(jsonPath("_links.note-tags", is(notNullValue())))
|
||||
.andDo(document("note-get-example")
|
||||
.withLinks(
|
||||
.andDo(document("note-get-example",
|
||||
links(
|
||||
linkWithRel("self").description("This <<resources-note,note>>"),
|
||||
linkWithRel("note-tags").description("This note's <<resources-note-tags,tags>>"))
|
||||
.withResponseFields(
|
||||
linkWithRel("note-tags").description("This note's <<resources-note-tags,tags>>")),
|
||||
responseFields(
|
||||
fieldWithPath("title").description("The title 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"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("tags-list-example")
|
||||
.withResponseFields(
|
||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>")));
|
||||
.andDo(document("tags-list-example",
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -221,9 +224,9 @@ public class ApiDocumentation {
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated())
|
||||
.andDo(document("tags-create-example")
|
||||
.withRequestFields(
|
||||
fieldWithPath("name").description("The name of the tag")));
|
||||
.andDo(document("tags-create-example",
|
||||
requestFields(
|
||||
fieldWithPath("name").description("The name of the tag"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -262,11 +265,11 @@ public class ApiDocumentation {
|
||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(noteUpdate)))
|
||||
.andExpect(status().isNoContent())
|
||||
.andDo(document("note-update-example")
|
||||
.withRequestFields(
|
||||
.andDo(document("note-update-example",
|
||||
requestFields(
|
||||
fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(),
|
||||
fieldWithPath("body").description("The body of the note").type(FieldType.STRING).optional(),
|
||||
fieldWithPath("tags").description("An array of tag resource URIs").optional()));
|
||||
fieldWithPath("tags").description("An array of tag resource URIs").optional())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -284,13 +287,13 @@ public class ApiDocumentation {
|
||||
this.mockMvc.perform(get(tagLocation))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("name", is(tag.get("name"))))
|
||||
.andDo(document("tag-get-example")
|
||||
.withLinks(
|
||||
.andDo(document("tag-get-example",
|
||||
links(
|
||||
linkWithRel("self").description("This <<resources-tag,tag>>"),
|
||||
linkWithRel("tagged-notes").description("The <<resources-tagged-notes,notes>> that have this tag"))
|
||||
.withResponseFields(
|
||||
linkWithRel("tagged-notes").description("The <<resources-tagged-notes,notes>> that have this tag")),
|
||||
responseFields(
|
||||
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
|
||||
@@ -312,9 +315,9 @@ public class ApiDocumentation {
|
||||
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tagUpdate)))
|
||||
.andExpect(status().isNoContent())
|
||||
.andDo(document("tag-update-example")
|
||||
.withRequestFields(
|
||||
fieldWithPath("name").description("The name of the tag")));
|
||||
.andDo(document("tag-update-example",
|
||||
requestFields(
|
||||
fieldWithPath("name").description("The name of the tag"))));
|
||||
}
|
||||
|
||||
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.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.restdocs.response.ResponsePostProcessor;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -47,19 +48,22 @@ public final class ResponseModifier {
|
||||
|
||||
/**
|
||||
* Provides a {@link RestDocumentationResultHandler} that can be used to document the
|
||||
* request and modified result.
|
||||
* @param identifier An identifier for the API call that is being documented
|
||||
* request and modified response.
|
||||
* @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
|
||||
*/
|
||||
public RestDocumentationResultHandler andDocument(String identifier) {
|
||||
return new ResponseModifyingRestDocumentationResultHandler(identifier);
|
||||
public RestDocumentationResultHandler andDocument(String identifier,
|
||||
Snippet... snippets) {
|
||||
return new ResponseModifyingRestDocumentationResultHandler(identifier, snippets);
|
||||
}
|
||||
|
||||
class ResponseModifyingRestDocumentationResultHandler extends
|
||||
RestDocumentationResultHandler {
|
||||
|
||||
public ResponseModifyingRestDocumentationResultHandler(String identifier) {
|
||||
super(identifier);
|
||||
private ResponseModifyingRestDocumentationResultHandler(String identifier,
|
||||
Snippet... snippets) {
|
||||
super(identifier, snippets);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.restdocs;
|
||||
import org.springframework.restdocs.config.RestDocumentationConfigurer;
|
||||
import org.springframework.restdocs.response.ResponsePostProcessor;
|
||||
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.MvcResult;
|
||||
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
|
||||
* REST documentation when building a {@link MockMvc} instance.
|
||||
*
|
||||
* @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)
|
||||
* @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
|
||||
* @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder)
|
||||
* @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler)
|
||||
*/
|
||||
public static RestDocumentationResultHandler document(String identifier) {
|
||||
return new RestDocumentationResultHandler(identifier);
|
||||
public static RestDocumentationResultHandler document(String identifier,
|
||||
Snippet... snippets) {
|
||||
return new RestDocumentationResultHandler(identifier, snippets);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.restdocs;
|
||||
import java.net.URI;
|
||||
|
||||
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.MockMultipartHttpServletRequestBuilder;
|
||||
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
|
||||
* template and makes it available for documentation. Required when
|
||||
* {@link RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs .request.ParameterDescriptor...)
|
||||
* documenting path parameters} and recommended for general usage.
|
||||
* {@link RequestDocumentation#pathParameters(org.springframework.restdocs.request.ParameterDescriptor...)
|
||||
* ) documenting path parameters} and recommended for general usage.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @see MockMvcRequestBuilders
|
||||
* @see RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs.request.ParameterDescriptor...)
|
||||
* @see RestDocumentationResultHandler#withPathParameters(java.util.Map,
|
||||
* @see RequestDocumentation#pathParameters(org.springframework.restdocs.request.ParameterDescriptor...)
|
||||
* @see RequestDocumentation#pathParameters(java.util.Map,
|
||||
* org.springframework.restdocs.request.ParameterDescriptor...)
|
||||
*/
|
||||
public abstract class RestDocumentationRequestBuilders {
|
||||
|
||||
@@ -16,28 +16,11 @@
|
||||
|
||||
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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.hypermedia.HypermediaDocumentation;
|
||||
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.restdocs.snippet.Snippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.ResultHandler;
|
||||
|
||||
@@ -46,317 +29,32 @@ import org.springframework.test.web.servlet.ResultHandler;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Andreas Evers
|
||||
* @see RestDocumentation#document(String)
|
||||
* @see RestDocumentation#document(String, Snippet...)
|
||||
*/
|
||||
public class RestDocumentationResultHandler implements ResultHandler {
|
||||
|
||||
private final String identifier;
|
||||
|
||||
private SnippetWritingResultHandler curlRequest;
|
||||
private final List<Snippet> snippets;
|
||||
|
||||
private SnippetWritingResultHandler httpRequest;
|
||||
|
||||
private SnippetWritingResultHandler httpResponse;
|
||||
|
||||
private List<SnippetWritingResultHandler> delegates = new ArrayList<>();
|
||||
|
||||
RestDocumentationResultHandler(String identifier) {
|
||||
RestDocumentationResultHandler(String identifier, Snippet... snippets) {
|
||||
this.identifier = identifier;
|
||||
this.curlRequest = documentCurlRequest(this.identifier, null);
|
||||
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;
|
||||
this.snippets = Arrays.asList(snippets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws Exception {
|
||||
this.curlRequest.handle(result);
|
||||
this.httpRequest.handle(result);
|
||||
this.httpResponse.handle(result);
|
||||
for (ResultHandler delegate : this.delegates) {
|
||||
delegate.handle(result);
|
||||
for (Snippet snippet : getSnippets(result)) {
|
||||
snippet.document(this.identifier, 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 {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
abstract void apply(MockHttpServletRequest request);
|
||||
|
||||
@@ -49,9 +49,9 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
|
||||
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}.
|
||||
@@ -65,19 +65,44 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
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() {
|
||||
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() {
|
||||
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) {
|
||||
this.templateEngineConfigurer.setTemplateEngine(templateEngine);
|
||||
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) {
|
||||
this.writerResolverConfigurer.setWriterResolver(writerResolver);
|
||||
return this;
|
||||
@@ -175,4 +200,5 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,10 +33,12 @@ public final class RestDocumentationContext {
|
||||
|
||||
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) {
|
||||
this.testContext = testContext;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,12 @@ public class RestDocumentationContextPlaceholderResolver implements PlaceholderR
|
||||
|
||||
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) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
|
||||
package org.springframework.restdocs.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -28,6 +34,10 @@ import org.springframework.restdocs.snippet.WriterResolver;
|
||||
public class SnippetConfigurer extends
|
||||
AbstractNestedConfigurer<RestDocumentationConfigurer> {
|
||||
|
||||
private List<Snippet> defaultSnippets = Arrays.asList(
|
||||
CurlDocumentation.curlRequest(), HttpDocumentation.httpRequest(),
|
||||
HttpDocumentation.httpResponse());
|
||||
|
||||
/**
|
||||
* The default encoding for documentation snippets
|
||||
* @see #withEncoding(String)
|
||||
@@ -43,7 +53,7 @@ public class SnippetConfigurer extends
|
||||
/**
|
||||
* Configures any documentation snippets to be written using the given
|
||||
* {@code encoding}. The default is UTF-8.
|
||||
* @param encoding The encoding
|
||||
* @param encoding the encoding
|
||||
* @return {@code this}
|
||||
*/
|
||||
public SnippetConfigurer withEncoding(String encoding) {
|
||||
@@ -55,5 +65,18 @@ public class SnippetConfigurer extends
|
||||
void apply(MockHttpServletRequest request) {
|
||||
((WriterResolver) request.getAttribute(WriterResolver.class.getName()))
|
||||
.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;
|
||||
|
||||
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.SnippetWritingResultHandler;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* snippet
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static SnippetWritingResultHandler documentCurlRequest(String identifier,
|
||||
Map<String, Object> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
public static Snippet curlRequest(Map<String, Object> attributes) {
|
||||
return new CurlRequestSnippet(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
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.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;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||
|
||||
private HttpDocumentation() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a documentation snippet containing the request formatted as an HTTP
|
||||
* request
|
||||
* Returns a handler that will produce a snippet containing the HTTP request for the
|
||||
* 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
|
||||
*/
|
||||
public static SnippetWritingResultHandler documentHttpRequest(String identifier,
|
||||
Map<String, Object> attributes) {
|
||||
return new HttpRequestWritingResultHandler(identifier, attributes);
|
||||
public static Snippet httpRequest() {
|
||||
return new HttpRequestSnippet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a documentation snippet containing the response formatted as the HTTP
|
||||
* response sent by the server
|
||||
*
|
||||
* @param identifier An identifier for the API call that is being documented
|
||||
* @param attributes Attributes made available during rendering of the HTTP response
|
||||
* snippet
|
||||
* Returns a handler that will produce a snippet containing the HTTP request for the
|
||||
* API call. The given {@code attributes} will be available during snippet generation.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static SnippetWritingResultHandler documentHttpResponse(String identifier,
|
||||
Map<String, Object> attributes) {
|
||||
return new HttpResponseWritingResultHandler(identifier, attributes);
|
||||
|
||||
public static Snippet httpRequest(Map<String, Object> attributes) {
|
||||
return new HttpRequestSnippet(attributes);
|
||||
}
|
||||
|
||||
private static final class HttpRequestWritingResultHandler extends
|
||||
SnippetWritingResultHandler {
|
||||
|
||||
private HttpRequestWritingResultHandler(String identifier,
|
||||
Map<String, Object> attributes) {
|
||||
super(identifier, "http-request", attributes);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
/**
|
||||
* Returns a handler that will produce a snippet containing the HTTP response for the
|
||||
* API call.
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static Snippet httpResponse() {
|
||||
return new HttpResponseSnippet();
|
||||
}
|
||||
|
||||
private static final class HttpResponseWritingResultHandler extends
|
||||
SnippetWritingResultHandler {
|
||||
|
||||
private HttpResponseWritingResultHandler(String identifier,
|
||||
Map<String, Object> attributes) {
|
||||
super(identifier, "http-response", attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static Snippet httpResponse(Map<String, Object> attributes) {
|
||||
return new HttpResponseSnippet(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.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
|
||||
*/
|
||||
@@ -37,29 +37,89 @@ public abstract class HypermediaDocumentation {
|
||||
*
|
||||
* @param rel The rel of the link
|
||||
* @return a {@code LinkDescriptor} ready for further configuration
|
||||
* @see RestDocumentationResultHandler#withLinks(LinkDescriptor...)
|
||||
* @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...)
|
||||
*/
|
||||
public static LinkDescriptor linkWithRel(String rel) {
|
||||
return new LinkDescriptor(rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code LinkSnippetResultHandler} that will produce a documentation
|
||||
* snippet for a response's links.
|
||||
* Returns a handler that will produce a snippet documenting the links in the API
|
||||
* 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 linkExtractor Used to extract the links from the response
|
||||
* @param descriptors The descriptions of the response's links
|
||||
* @return the handler
|
||||
* @see RestDocumentationResultHandler#withLinks(LinkDescriptor...)
|
||||
* @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...)
|
||||
*/
|
||||
public static LinkSnippetResultHandler documentLinks(String identifier,
|
||||
Map<String, Object> attributes, LinkExtractor linkExtractor,
|
||||
LinkDescriptor... descriptors) {
|
||||
return new LinkSnippetResultHandler(identifier, attributes, linkExtractor,
|
||||
public static Snippet links(LinkExtractor linkExtractor,
|
||||
Map<String, Object> attributes, LinkDescriptor... descriptors) {
|
||||
return new LinksSnippet(linkExtractor, attributes,
|
||||
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.Set;
|
||||
|
||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful
|
||||
* resource's links.
|
||||
* A {@link Snippet} that documents a RESTful resource's links.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
||||
class LinksSnippet extends TemplatedSnippet {
|
||||
|
||||
private final Map<String, LinkDescriptor> descriptorsByRel = new LinkedHashMap<>();
|
||||
|
||||
private final Set<String> requiredRels = new HashSet<>();
|
||||
|
||||
private final LinkExtractor extractor;
|
||||
private final LinkExtractor linkExtractor;
|
||||
|
||||
LinkSnippetResultHandler(String identifier, Map<String, Object> attributes,
|
||||
LinkExtractor linkExtractor, List<LinkDescriptor> descriptors) {
|
||||
super(identifier, "links", attributes);
|
||||
this.extractor = linkExtractor;
|
||||
LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors) {
|
||||
this(linkExtractor, null, descriptors);
|
||||
}
|
||||
|
||||
LinksSnippet(LinkExtractor linkExtractor, Map<String, Object> attributes,
|
||||
List<LinkDescriptor> descriptors) {
|
||||
super("links", attributes);
|
||||
this.linkExtractor = linkExtractor;
|
||||
for (LinkDescriptor descriptor : descriptors) {
|
||||
Assert.hasText(descriptor.getRel());
|
||||
Assert.hasText(descriptor.getDescription());
|
||||
@@ -60,31 +64,13 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> doHandle(MvcResult result) throws IOException {
|
||||
validate(extractLinks(result));
|
||||
protected Map<String, Object> document(MvcResult result) throws IOException {
|
||||
validate(this.linkExtractor.extractLinks(result.getResponse()));
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("links", createLinksModel());
|
||||
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) {
|
||||
Set<String> actualRels = links.keySet();
|
||||
|
||||
@@ -104,10 +90,10 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
||||
if (message.length() > 0) {
|
||||
message += ". ";
|
||||
}
|
||||
message += "Links with the following relations were not found in the response: "
|
||||
+ missingRels;
|
||||
message += "Links with the following relations were not found in the "
|
||||
+ "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.Entry;
|
||||
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful
|
||||
* resource's request or response fields.
|
||||
* A {@link TemplatedSnippet} that produces a snippet documenting a
|
||||
* RESTful resource's request or response fields.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
* @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>();
|
||||
|
||||
@@ -50,9 +51,9 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
||||
|
||||
private List<FieldDescriptor> fieldDescriptors;
|
||||
|
||||
FieldSnippetResultHandler(String identifier, String type,
|
||||
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||
super(identifier, type + "-fields", attributes);
|
||||
AbstractFieldsSnippet(String type, Map<String, Object> attributes,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
super(type + "-fields", attributes);
|
||||
for (FieldDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getPath());
|
||||
Assert.hasText(descriptor.getDescription());
|
||||
@@ -62,7 +63,7 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
||||
}
|
||||
|
||||
@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);
|
||||
Object payload = extractPayload(result);
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
@@ -80,8 +81,8 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
||||
|
||||
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
||||
try {
|
||||
return FieldSnippetResultHandler.this.fieldTypeResolver.resolveFieldType(
|
||||
descriptor.getPath(), payload);
|
||||
return AbstractFieldsSnippet.this.fieldTypeResolver
|
||||
.resolveFieldType(descriptor.getPath(), payload);
|
||||
}
|
||||
catch (FieldDoesNotExistException ex) {
|
||||
String message = "Cannot determine the type of the field '"
|
||||
@@ -22,7 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.SerializationFeature;
|
||||
@@ -60,7 +60,7 @@ class FieldValidator {
|
||||
message += "Fields with the following paths were not found in the payload: "
|
||||
+ 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.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.
|
||||
@@ -89,57 +89,92 @@ public abstract class PayloadDocumentation {
|
||||
*
|
||||
* @param path The path of the field
|
||||
* @return a {@code FieldDescriptor} ready for further configuration
|
||||
* @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...)
|
||||
* @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...)
|
||||
*/
|
||||
public static FieldDescriptor fieldWithPath(String path) {
|
||||
return new FieldDescriptor(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code RequestFieldsSnippetResultHandler} that will produce a
|
||||
* documentation snippet for a request's fields.
|
||||
* Returns a handler that will produce a snippet documenting the fields of the API
|
||||
* call's request.
|
||||
* <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
|
||||
* 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 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
|
||||
* @return the handler
|
||||
* @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...)
|
||||
* @see #fieldWithPath(String)
|
||||
*/
|
||||
public static FieldSnippetResultHandler documentRequestFields(String identifier,
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
return new RequestFieldSnippetResultHandler(identifier, attributes,
|
||||
public static Snippet requestFields(FieldDescriptor... descriptors) {
|
||||
return new RequestFieldsSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ResponseFieldsSnippetResultHandler} that will produce a
|
||||
* documentation snippet for a response's fields.
|
||||
* Returns a handler that will produce a snippet documenting the fields of the API
|
||||
* call's response.
|
||||
* <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
|
||||
* 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 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
|
||||
* @return the handler
|
||||
* @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...)
|
||||
* @see #fieldWithPath(String)
|
||||
*/
|
||||
public static FieldSnippetResultHandler documentResponseFields(String identifier,
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
return new ResponseFieldSnippetResultHandler(identifier, attributes,
|
||||
public static Snippet responseFields(FieldDescriptor... descriptors) {
|
||||
return new ResponseFieldsSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,18 +20,22 @@ import java.io.Reader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
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
|
||||
*/
|
||||
public class RequestFieldSnippetResultHandler extends FieldSnippetResultHandler {
|
||||
class RequestFieldsSnippet extends AbstractFieldsSnippet {
|
||||
|
||||
RequestFieldSnippetResultHandler(String identifier, Map<String, Object> attributes,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
super(identifier, "request", attributes, descriptors);
|
||||
RequestFieldsSnippet(List<FieldDescriptor> descriptors) {
|
||||
this(null, descriptors);
|
||||
}
|
||||
|
||||
RequestFieldsSnippet(Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||
super("request", attributes, descriptors);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -21,18 +21,23 @@ import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
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
|
||||
*/
|
||||
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) {
|
||||
super(identifier, "response", attributes, descriptors);
|
||||
super("response", attributes, descriptors);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -10,19 +10,18 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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.util.Assert;
|
||||
|
||||
public abstract class AbstractParametersSnippetResultHandler extends
|
||||
SnippetWritingResultHandler {
|
||||
abstract class AbstractParametersSnippet extends
|
||||
TemplatedSnippet {
|
||||
|
||||
private final Map<String, ParameterDescriptor> descriptorsByName = new LinkedHashMap<>();
|
||||
|
||||
protected AbstractParametersSnippetResultHandler(String identifier,
|
||||
String snippetName, Map<String, Object> attributes,
|
||||
ParameterDescriptor... descriptors) {
|
||||
super(identifier, snippetName, attributes);
|
||||
protected AbstractParametersSnippet(String snippetName,
|
||||
Map<String, Object> attributes, List<ParameterDescriptor> descriptors) {
|
||||
super(snippetName, attributes);
|
||||
for (ParameterDescriptor descriptor : descriptors) {
|
||||
Assert.hasText(descriptor.getName());
|
||||
Assert.hasText(descriptor.getDescription());
|
||||
@@ -31,7 +30,7 @@ public abstract class AbstractParametersSnippetResultHandler extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> doHandle(MvcResult result) throws IOException {
|
||||
protected Map<String, Object> document(MvcResult result) throws IOException {
|
||||
verifyParameterDescriptors(result);
|
||||
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
@@ -17,30 +17,33 @@
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting the path
|
||||
* parameters supported by a RESTful resource.
|
||||
* A {@link Snippet} that documents the path parameters supported by a RESTful resource.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class PathParametersSnippetResultHandler extends
|
||||
AbstractParametersSnippetResultHandler {
|
||||
class PathParametersSnippet extends AbstractParametersSnippet {
|
||||
|
||||
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
|
||||
|
||||
protected PathParametersSnippetResultHandler(String identifier,
|
||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
||||
super(identifier, "path-parameters", attributes, descriptors);
|
||||
PathParametersSnippet(List<ParameterDescriptor> descriptors) {
|
||||
this(null, descriptors);
|
||||
}
|
||||
|
||||
PathParametersSnippet(Map<String, Object> attributes,
|
||||
List<ParameterDescriptor> descriptors) {
|
||||
super("path-parameters", attributes, descriptors);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +87,7 @@ public class PathParametersSnippetResultHandler extends
|
||||
message += "Path parameters with the following names were not found in "
|
||||
+ "the request: " + missingParameters;
|
||||
}
|
||||
throw new SnippetGenerationException(message);
|
||||
throw new SnippetException(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,25 +16,28 @@
|
||||
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
/**
|
||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting the query
|
||||
* parameters supported by a RESTful resource.
|
||||
* A {@link Snippet} that documents the query parameters supported by a RESTful resource.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class QueryParametersSnippetResultHandler extends
|
||||
AbstractParametersSnippetResultHandler {
|
||||
class QueryParametersSnippet extends AbstractParametersSnippet {
|
||||
|
||||
protected QueryParametersSnippetResultHandler(String identifier,
|
||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
||||
super(identifier, "query-parameters", attributes, descriptors);
|
||||
QueryParametersSnippet(List<ParameterDescriptor> descriptors) {
|
||||
this(null, descriptors);
|
||||
}
|
||||
|
||||
QueryParametersSnippet(Map<String, Object> attributes,
|
||||
List<ParameterDescriptor> descriptors) {
|
||||
super("query-parameters", attributes, descriptors);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +55,7 @@ public class QueryParametersSnippetResultHandler extends
|
||||
message += "Query parameters with the following names were not found in the request: "
|
||||
+ missingParameters;
|
||||
}
|
||||
throw new SnippetGenerationException(message);
|
||||
throw new SnippetException(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.RestDocumentationResultHandler;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* the given {@code name}.
|
||||
*
|
||||
* @param name The name of the parameter
|
||||
* @return a {@link ParameterDescriptor} ready for further configuration
|
||||
* @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...)
|
||||
*/
|
||||
public static ParameterDescriptor parameterWithName(String 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;
|
||||
|
||||
public ContentModifyingMethodInterceptor(String modifiedContent,
|
||||
private ContentModifyingMethodInterceptor(String modifiedContent,
|
||||
MockHttpServletResponse delegate) {
|
||||
this.modifiedContent = modifiedContent;
|
||||
this.delegate = delegate;
|
||||
|
||||
@@ -42,7 +42,7 @@ class HeaderRemovingResponsePostProcessor implements ResponsePostProcessor {
|
||||
|
||||
private final Set<String> headersToRemove;
|
||||
|
||||
public HeaderRemovingResponsePostProcessor(String... headersToRemove) {
|
||||
HeaderRemovingResponsePostProcessor(String... headersToRemove) {
|
||||
this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove));
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,11 @@ public abstract class Attributes {
|
||||
public Attribute value(Object value) {
|
||||
return new Attribute(this.key, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An attribute (key-value pair).
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public static class Attribute {
|
||||
|
||||
@@ -88,18 +87,32 @@ public abstract class Attributes {
|
||||
|
||||
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) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute's key
|
||||
* @return the key
|
||||
*/
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute's value
|
||||
* @return the value
|
||||
*/
|
||||
public Object getValue() {
|
||||
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
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SnippetGenerationException extends RuntimeException {
|
||||
public class SnippetException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new {@code SnippetGenerationException} described by the given
|
||||
* {@code message}
|
||||
* Creates a new {@code SnippetException} described by the given {@code message}
|
||||
* @param message the message that describes the problem
|
||||
*/
|
||||
public SnippetGenerationException(String message) {
|
||||
public SnippetException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -21,26 +21,23 @@ import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.templates.Template;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
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
|
||||
*/
|
||||
public abstract class SnippetWritingResultHandler implements ResultHandler {
|
||||
public abstract class TemplatedSnippet implements Snippet {
|
||||
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
private final String identifier;
|
||||
|
||||
private final String snippetName;
|
||||
|
||||
protected SnippetWritingResultHandler(String identifier, String snippetName,
|
||||
Map<String, Object> attributes) {
|
||||
this.identifier = identifier;
|
||||
protected TemplatedSnippet(String snippetName, Map<String, Object> attributes) {
|
||||
this.snippetName = snippetName;
|
||||
if (attributes != null) {
|
||||
this.attributes.putAll(attributes);
|
||||
@@ -48,11 +45,11 @@ public abstract class SnippetWritingResultHandler implements ResultHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws IOException {
|
||||
public void document(String operation, MvcResult result) throws IOException {
|
||||
WriterResolver writerResolver = (WriterResolver) result.getRequest()
|
||||
.getAttribute(WriterResolver.class.getName());
|
||||
try (Writer writer = writerResolver.resolve(this.identifier, this.snippetName)) {
|
||||
Map<String, Object> model = doHandle(result);
|
||||
try (Writer writer = writerResolver.resolve(operation, this.snippetName)) {
|
||||
Map<String, Object> model = document(result);
|
||||
model.putAll(this.attributes);
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.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.modifyResponseTo;
|
||||
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.links;
|
||||
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.pathParameters;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
|
||||
import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks;
|
||||
import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent;
|
||||
import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders;
|
||||
@@ -112,14 +118,14 @@ public class RestDocumentationIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void links() throws Exception {
|
||||
public void linksSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("links").withLinks(
|
||||
linkWithRel("rel").description("The description")));
|
||||
.andDo(document("links",
|
||||
links(linkWithRel("rel").description("The description"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||
@@ -127,14 +133,14 @@ public class RestDocumentationIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathParameters() throws Exception {
|
||||
public void pathParametersSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
mockMvc.perform(get("{foo}", "/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("links").withPathParameters(
|
||||
parameterWithName("foo").description("The description")));
|
||||
.andDo(document("links", pathParameters(parameterWithName("foo")
|
||||
.description("The description"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||
@@ -142,14 +148,14 @@ public class RestDocumentationIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParameters() throws Exception {
|
||||
public void queryParametersSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("links").withQueryParameters(
|
||||
parameterWithName("foo").description("The description")));
|
||||
.andDo(document("links", queryParameters(parameterWithName("foo")
|
||||
.description("The description"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||
@@ -157,7 +163,7 @@ public class RestDocumentationIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestFields() throws Exception {
|
||||
public void requestFieldsSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
@@ -165,8 +171,8 @@ public class RestDocumentationIntegrationTests {
|
||||
get("/").param("foo", "bar").content("{\"a\":\"alpha\"}")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("links").withRequestFields(
|
||||
fieldWithPath("a").description("The description")));
|
||||
.andDo(document("links",
|
||||
requestFields(fieldWithPath("a").description("The description"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||
@@ -174,15 +180,18 @@ public class RestDocumentationIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseFields() throws Exception {
|
||||
public void responseFieldsSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("links").withResponseFields(
|
||||
fieldWithPath("a").description("The description"),
|
||||
fieldWithPath("links").description("Links to other resources")));
|
||||
.andDo(document(
|
||||
"links",
|
||||
responseFields(
|
||||
fieldWithPath("a").description("The description"),
|
||||
fieldWithPath("links").description(
|
||||
"Links to other resources"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||
@@ -284,8 +293,10 @@ public class RestDocumentationIntegrationTests {
|
||||
is(snippet().withContents(equalTo("Custom curl request"))));
|
||||
|
||||
mockMvc.perform(get("/")).andDo(
|
||||
document("index").withCurlRequest(
|
||||
attributes(key("title").value("Access the index using curl"))));
|
||||
document(
|
||||
"index",
|
||||
curlRequest(attributes(key("title").value(
|
||||
"Access the index using curl")))));
|
||||
}
|
||||
|
||||
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class RestDocumentationConfigurerTests {
|
||||
|
||||
private MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
private RestDocumentationContext context = new RestDocumentationContext();
|
||||
private RestDocumentationContext context = new RestDocumentationContext(null);
|
||||
|
||||
@Before
|
||||
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.post;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.put;
|
||||
import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
|
||||
@@ -44,14 +43,14 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||
|
||||
/**
|
||||
* Tests for {@link CurlDocumentation}
|
||||
* Tests for {@link CurlRequestSnippet}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Yann Le Guern
|
||||
* @author Dmitriy Mayboroda
|
||||
* @author Jonathan Pearlin
|
||||
*/
|
||||
public class CurlDocumentationTests {
|
||||
public class CurlRequestDocumentationHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedSnippet snippet = new ExpectedSnippet();
|
||||
@@ -63,14 +62,16 @@ public class CurlDocumentationTests {
|
||||
public void getRequest() throws IOException {
|
||||
this.snippet.expectCurlRequest("get-request").withContents(
|
||||
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
|
||||
public void nonGetRequest() throws IOException {
|
||||
this.snippet.expectCurlRequest("non-get-request").withContents(
|
||||
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
|
||||
@@ -78,7 +79,7 @@ public class CurlDocumentationTests {
|
||||
this.snippet.expectCurlRequest("request-with-content").withContents(
|
||||
codeBlock("bash")
|
||||
.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")));
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ public class CurlDocumentationTests {
|
||||
.withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ 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")));
|
||||
}
|
||||
|
||||
@@ -96,7 +97,7 @@ public class CurlDocumentationTests {
|
||||
public void requestWithOneParameter() throws IOException {
|
||||
this.snippet.expectCurlRequest("request-with-one-parameter").withContents(
|
||||
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")));
|
||||
}
|
||||
|
||||
@@ -105,9 +106,9 @@ public class CurlDocumentationTests {
|
||||
this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i"));
|
||||
documentCurlRequest("request-with-multiple-parameters", null).handle(
|
||||
result(get("/foo").param("k1", "v1").param("k2", "v2")
|
||||
.param("k1", "v1-bis")));
|
||||
new CurlRequestSnippet().document(
|
||||
"request-with-multiple-parameters", result(get("/foo").param("k1", "v1")
|
||||
.param("k2", "v2").param("k1", "v1-bis")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,7 +117,8 @@ public class CurlDocumentationTests {
|
||||
.withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ 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&")));
|
||||
}
|
||||
|
||||
@@ -125,7 +127,7 @@ public class CurlDocumentationTests {
|
||||
this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ 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")));
|
||||
}
|
||||
|
||||
@@ -136,7 +138,8 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content(
|
||||
"$ curl 'http://localhost/foo' -i -X POST"
|
||||
+ " -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")));
|
||||
}
|
||||
|
||||
@@ -147,7 +150,8 @@ public class CurlDocumentationTests {
|
||||
.withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ 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")));
|
||||
}
|
||||
|
||||
@@ -156,7 +160,7 @@ public class CurlDocumentationTests {
|
||||
this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ 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")));
|
||||
}
|
||||
|
||||
@@ -167,7 +171,8 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content(
|
||||
"$ curl 'http://localhost/foo' -i -X PUT"
|
||||
+ " -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")));
|
||||
}
|
||||
|
||||
@@ -177,7 +182,8 @@ public class CurlDocumentationTests {
|
||||
.withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ 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")));
|
||||
}
|
||||
|
||||
@@ -187,7 +193,8 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content(
|
||||
"$ curl 'http://localhost/foo' -i"
|
||||
+ " -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",
|
||||
"alpha")));
|
||||
}
|
||||
@@ -198,7 +205,8 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerPort(8080);
|
||||
documentCurlRequest("http-with-non-standard-port", null).handle(result(request));
|
||||
new CurlRequestSnippet().document("http-with-non-standard-port",
|
||||
result(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -208,7 +216,8 @@ public class CurlDocumentationTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerPort(443);
|
||||
request.setScheme("https");
|
||||
documentCurlRequest("https-with-standard-port", null).handle(result(request));
|
||||
new CurlRequestSnippet().document("https-with-standard-port",
|
||||
result(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -218,7 +227,8 @@ public class CurlDocumentationTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerPort(8443);
|
||||
request.setScheme("https");
|
||||
documentCurlRequest("https-with-non-standard-port", null).handle(result(request));
|
||||
new CurlRequestSnippet().document("https-with-non-standard-port",
|
||||
result(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,7 +237,8 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerName("api.example.com");
|
||||
documentCurlRequest("request-with-custom-host", null).handle(result(request));
|
||||
new CurlRequestSnippet().document("request-with-custom-host",
|
||||
result(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -239,8 +250,8 @@ public class CurlDocumentationTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerName("api.example.com");
|
||||
request.setContextPath("/v3");
|
||||
documentCurlRequest("request-with-custom-context-with-slash", null).handle(
|
||||
result(request));
|
||||
new CurlRequestSnippet().document(
|
||||
"request-with-custom-context-with-slash", result(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -252,8 +263,8 @@ public class CurlDocumentationTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerName("api.example.com");
|
||||
request.setContextPath("v3");
|
||||
documentCurlRequest("request-with-custom-context-without-slash", null).handle(
|
||||
result(request));
|
||||
new CurlRequestSnippet().document(
|
||||
"request-with-custom-context-without-slash", result(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -265,7 +276,8 @@ public class CurlDocumentationTests {
|
||||
.withContents(codeBlock("bash").content(expectedContent));
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("metadata",
|
||||
"{\"description\": \"foo\"}".getBytes());
|
||||
documentCurlRequest("multipart-post-no-original-filename", null).handle(
|
||||
new CurlRequestSnippet().document(
|
||||
"multipart-post-no-original-filename",
|
||||
result(fileUpload("/upload").file(multipartFile)));
|
||||
}
|
||||
|
||||
@@ -279,7 +291,8 @@ public class CurlDocumentationTests {
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||
"documents/images/example.png", MediaType.IMAGE_PNG_VALUE,
|
||||
"bytes".getBytes());
|
||||
documentCurlRequest("multipart-post-with-content-type", null).handle(
|
||||
new CurlRequestSnippet().document(
|
||||
"multipart-post-with-content-type",
|
||||
result(fileUpload("/upload").file(multipartFile)));
|
||||
}
|
||||
|
||||
@@ -292,7 +305,7 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content(expectedContent));
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||
"documents/images/example.png", null, "bytes".getBytes());
|
||||
documentCurlRequest("multipart-post", null).handle(
|
||||
new CurlRequestSnippet().document("multipart-post",
|
||||
result(fileUpload("/upload").file(multipartFile)));
|
||||
}
|
||||
|
||||
@@ -306,7 +319,8 @@ public class CurlDocumentationTests {
|
||||
codeBlock("bash").content(expectedContent));
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||
"documents/images/example.png", null, "bytes".getBytes());
|
||||
documentCurlRequest("multipart-post", null).handle(
|
||||
new CurlRequestSnippet().document(
|
||||
"multipart-post",
|
||||
result(fileUpload("/upload").file(multipartFile)
|
||||
.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"));
|
||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||
resolver));
|
||||
documentCurlRequest("custom-attributes",
|
||||
attributes(key("title").value("curl request title"))).handle(
|
||||
result(request));
|
||||
new CurlRequestSnippet(attributes(key("title").value(
|
||||
"curl request title"))).document("custom-attributes", result(request));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,18 +19,13 @@ 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.RestDocumentationRequestBuilders.fileUpload;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.get;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.post;
|
||||
import static org.springframework.restdocs.RestDocumentationRequestBuilders.put;
|
||||
import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest;
|
||||
import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.httpRequest;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
|
||||
import static org.springframework.restdocs.test.StubMvcResult.result;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||
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.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
@@ -52,12 +46,13 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpDocumentation}
|
||||
*
|
||||
* Tests for {@link HttpRequestSnippet}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Jonathan Pearlin
|
||||
*
|
||||
*/
|
||||
public class HttpDocumentationTests {
|
||||
public class HttpRequestDocumentationHandlerTests {
|
||||
|
||||
private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||
|
||||
@@ -70,8 +65,8 @@ public class HttpDocumentationTests {
|
||||
httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header(
|
||||
"Alpha", "a"));
|
||||
|
||||
documentHttpRequest("get-request", null).handle(
|
||||
result(get("/foo").header("Alpha", "a")));
|
||||
new HttpRequestSnippet().document("get-request", result(get("/foo")
|
||||
.header("Alpha", "a")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,7 +74,7 @@ public class HttpDocumentationTests {
|
||||
this.snippet.expectHttpRequest("get-request-with-query-string").withContents(
|
||||
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")));
|
||||
}
|
||||
|
||||
@@ -88,7 +83,7 @@ public class HttpDocumentationTests {
|
||||
this.snippet.expectHttpRequest("get-request-with-parameter").withContents(
|
||||
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")));
|
||||
}
|
||||
|
||||
@@ -98,7 +93,7 @@ public class HttpDocumentationTests {
|
||||
httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content(
|
||||
"Hello, world"));
|
||||
|
||||
documentHttpRequest("post-request-with-content", null).handle(
|
||||
new HttpRequestSnippet().document("post-request-with-content",
|
||||
result(post("/foo").content("Hello, world")));
|
||||
}
|
||||
|
||||
@@ -109,7 +104,7 @@ public class HttpDocumentationTests {
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.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")));
|
||||
}
|
||||
|
||||
@@ -119,7 +114,7 @@ public class HttpDocumentationTests {
|
||||
httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content(
|
||||
"Hello, world"));
|
||||
|
||||
documentHttpRequest("put-request-with-content", null).handle(
|
||||
new HttpRequestSnippet().document("put-request-with-content",
|
||||
result(put("/foo").content("Hello, world")));
|
||||
}
|
||||
|
||||
@@ -130,48 +125,10 @@ public class HttpDocumentationTests {
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.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")));
|
||||
}
|
||||
|
||||
@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
|
||||
public void multipartPost() throws IOException {
|
||||
String expectedContent = createPart(String.format("Content-Disposition: "
|
||||
@@ -184,7 +141,7 @@ public class HttpDocumentationTests {
|
||||
.content(expectedContent));
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||
"documents/images/example.png", null, "<< data >>".getBytes());
|
||||
documentHttpRequest("multipart-post", null).handle(
|
||||
new HttpRequestSnippet().document("multipart-post",
|
||||
result(fileUpload("/upload").file(multipartFile)));
|
||||
}
|
||||
|
||||
@@ -207,7 +164,8 @@ public class HttpDocumentationTests {
|
||||
.content(expectedContent));
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||
"documents/images/example.png", null, "<< data >>".getBytes());
|
||||
documentHttpRequest("multipart-post", null).handle(
|
||||
new HttpRequestSnippet().document(
|
||||
"multipart-post",
|
||||
result(fileUpload("/upload").file(multipartFile)
|
||||
.param("a", "apple", "avocado").param("b", "banana")));
|
||||
}
|
||||
@@ -226,7 +184,8 @@ public class HttpDocumentationTests {
|
||||
MockMultipartFile multipartFile = new MockMultipartFile("image",
|
||||
"documents/images/example.png", MediaType.IMAGE_PNG_VALUE,
|
||||
"<< data >>".getBytes());
|
||||
documentHttpRequest("multipart-post-with-content-type", null).handle(
|
||||
new HttpRequestSnippet().document(
|
||||
"multipart-post-with-content-type",
|
||||
result(fileUpload("/upload").file(multipartFile)));
|
||||
}
|
||||
|
||||
@@ -238,7 +197,7 @@ public class HttpDocumentationTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.setServerName("api.example.com");
|
||||
|
||||
documentHttpRequest("get-request-custom-server-name", null).handle(
|
||||
new HttpRequestSnippet().document("get-request-custom-server-name",
|
||||
result(request));
|
||||
}
|
||||
|
||||
@@ -247,7 +206,7 @@ public class HttpDocumentationTests {
|
||||
this.snippet.expectHttpRequest("get-request-custom-host").withContents(
|
||||
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")));
|
||||
}
|
||||
|
||||
@@ -263,25 +222,8 @@ public class HttpDocumentationTests {
|
||||
"src/test/resources/custom-snippet-templates/http-request-with-title.snippet"));
|
||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||
resolver));
|
||||
documentHttpRequest("request-with-snippet-attributes",
|
||||
attributes(key("title").value("Title for the request"))).handle(
|
||||
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(
|
||||
new HttpRequestSnippet(attributes(key("title").value(
|
||||
"Title for the request"))).document("request-with-snippet-attributes",
|
||||
result(request));
|
||||
}
|
||||
|
||||
@@ -290,12 +232,12 @@ public class HttpDocumentationTests {
|
||||
}
|
||||
|
||||
private String createPart(String content, boolean last) {
|
||||
String boundary = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||
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) {
|
||||
part.append(String.format("--%s--", boundary));
|
||||
part.append(String.format("--%s--", BOUNDARY));
|
||||
}
|
||||
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;
|
||||
|
||||
/**
|
||||
* Parameterized tests for {@link LinkExtractors} with various payloads.
|
||||
* Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with
|
||||
* various payloads.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@@ -50,8 +51,8 @@ public class LinkExtractorsPayloadTests {
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[] { LinkExtractors.halLinks(), "hal" },
|
||||
new Object[] { LinkExtractors.atomLinks(), "atom" });
|
||||
return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" },
|
||||
new Object[] { new AtomLinkExtractor(), "atom" });
|
||||
}
|
||||
|
||||
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.mockito.Mockito.mock;
|
||||
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.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;
|
||||
@@ -34,7 +35,7 @@ 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.snippet.SnippetException;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
@@ -43,11 +44,11 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Tests for {@link HypermediaDocumentation}
|
||||
* Tests for {@link LinksSnippet}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class HypermediaDocumentationTests {
|
||||
public class LinksDocumentationHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedSnippet snippet = new ExpectedSnippet();
|
||||
@@ -57,21 +58,22 @@ public class HypermediaDocumentationTests {
|
||||
|
||||
@Test
|
||||
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"
|
||||
+ " documented: [foo]"));
|
||||
documentLinks("undocumented-link", null,
|
||||
new StubLinkExtractor().withLinks(new Link("foo", "bar"))).handle(
|
||||
result());
|
||||
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo",
|
||||
"bar")), Collections.<LinkDescriptor> emptyList()).document(
|
||||
"undocumented-link", result());
|
||||
}
|
||||
|
||||
@Test
|
||||
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"
|
||||
+ " found in the response: [foo]"));
|
||||
documentLinks("missing-link", null, new StubLinkExtractor(),
|
||||
new LinkDescriptor("foo").description("bar")).handle(result());
|
||||
new LinksSnippet(new StubLinkExtractor(),
|
||||
Arrays.asList(new LinkDescriptor("foo").description("bar"))).document(
|
||||
"missing-link", result());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,9 +81,9 @@ public class HypermediaDocumentationTests {
|
||||
this.snippet.expectLinks("documented-optional-link").withContents( //
|
||||
tableWithHeader("Relation", "Description") //
|
||||
.row("foo", "bar"));
|
||||
documentLinks("documented-optional-link", null,
|
||||
new StubLinkExtractor().withLinks(new Link("foo", "blah")),
|
||||
new LinkDescriptor("foo").description("bar").optional()).handle(result());
|
||||
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo",
|
||||
"blah")), Arrays.asList(new LinkDescriptor("foo").description("bar")
|
||||
.optional())).document("documented-optional-link", result());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -89,19 +91,20 @@ public class HypermediaDocumentationTests {
|
||||
this.snippet.expectLinks("missing-optional-link").withContents( //
|
||||
tableWithHeader("Relation", "Description") //
|
||||
.row("foo", "bar"));
|
||||
documentLinks("missing-optional-link", null, new StubLinkExtractor(),
|
||||
new LinkDescriptor("foo").description("bar").optional()).handle(result());
|
||||
new LinksSnippet(new StubLinkExtractor(),
|
||||
Arrays.asList(new LinkDescriptor("foo").description("bar").optional()))
|
||||
.document("missing-optional-link", result());
|
||||
}
|
||||
|
||||
@Test
|
||||
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"
|
||||
+ " documented: [a]. Links with the following relations were not"
|
||||
+ " found in the response: [foo]"));
|
||||
documentLinks("undocumented-link-and-missing-link", null,
|
||||
new StubLinkExtractor().withLinks(new Link("a", "alpha")),
|
||||
new LinkDescriptor("foo").description("bar")).handle(result());
|
||||
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||
"alpha")), Arrays.asList(new LinkDescriptor("foo").description("bar")))
|
||||
.document("undocumented-link-and-missing-link", result());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,12 +113,11 @@ public class HypermediaDocumentationTests {
|
||||
tableWithHeader("Relation", "Description") //
|
||||
.row("a", "one") //
|
||||
.row("b", "two"));
|
||||
documentLinks(
|
||||
"documented-links",
|
||||
null,
|
||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
|
||||
"bravo")), new LinkDescriptor("a").description("one"),
|
||||
new LinkDescriptor("b").description("two")).handle(result());
|
||||
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||
"alpha"), new Link("b", "bravo")), Arrays.asList(
|
||||
new LinkDescriptor("a").description("one"),
|
||||
new LinkDescriptor("b").description("two"))).document("documented-links",
|
||||
result());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -132,20 +134,18 @@ public class HypermediaDocumentationTests {
|
||||
"src/test/resources/custom-snippet-templates/links-with-extra-column.snippet"));
|
||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||
resolver));
|
||||
documentLinks(
|
||||
"links-with-custom-descriptor-attributes",
|
||||
null,
|
||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
|
||||
"bravo")),
|
||||
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||
"alpha"), new Link("b", "bravo")), Arrays.asList(
|
||||
new LinkDescriptor("a").description("one").attributes(
|
||||
key("foo").value("alpha")),
|
||||
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
|
||||
public void linksWithCustomAttribute() throws IOException {
|
||||
this.snippet.expectLinks("links-with-custom-attribute").withContents(
|
||||
public void linksWithCustomAttributes() throws IOException {
|
||||
this.snippet.expectLinks("links-with-custom-attributes").withContents(
|
||||
startsWith(".Title for the links"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
@@ -155,12 +155,12 @@ public class HypermediaDocumentationTests {
|
||||
"src/test/resources/custom-snippet-templates/links-with-title.snippet"));
|
||||
request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(
|
||||
resolver));
|
||||
documentLinks(
|
||||
"links-with-custom-attribute",
|
||||
attributes(key("title").value("Title for the links")),
|
||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b",
|
||||
"bravo")), new LinkDescriptor("a").description("one"),
|
||||
new LinkDescriptor("b").description("two")).handle(result(request));
|
||||
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a",
|
||||
"alpha"), new Link("b", "bravo")), attributes(key("title").value(
|
||||
"Title for the links")), Arrays.asList(
|
||||
new LinkDescriptor("a").description("one"),
|
||||
new LinkDescriptor("b").description("two"))).document(
|
||||
"links-with-custom-attributes", result(request));
|
||||
}
|
||||
|
||||
private static class StubLinkExtractor implements LinkExtractor {
|
||||
@@ -24,7 +24,7 @@ import java.util.Arrays;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldValidator}
|
||||
@@ -73,7 +73,7 @@ public class FieldValidatorTests {
|
||||
|
||||
@Test
|
||||
public void missingField() throws IOException {
|
||||
this.thrownException.expect(SnippetGenerationException.class);
|
||||
this.thrownException.expect(SnippetException.class);
|
||||
this.thrownException
|
||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||
+ " in the payload: [y, z]"));
|
||||
@@ -84,7 +84,7 @@ public class FieldValidatorTests {
|
||||
|
||||
@Test
|
||||
public void undocumentedField() throws IOException {
|
||||
this.thrownException.expect(SnippetGenerationException.class);
|
||||
this.thrownException.expect(SnippetException.class);
|
||||
this.thrownException.expectMessage(equalTo(String
|
||||
.format("The following parts of the payload were not"
|
||||
+ " 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 org.junit.Test;
|
||||
import org.springframework.restdocs.snippet.StandardWriterResolver;
|
||||
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,12 +26,12 @@ import org.hamcrest.Matcher;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StubMvcResult implements MvcResult {
|
||||
this.request.setAttribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(new StandardTemplateResourceResolver()));
|
||||
}
|
||||
RestDocumentationContext context = new RestDocumentationContext();
|
||||
RestDocumentationContext context = new RestDocumentationContext(null);
|
||||
this.request.setAttribute(RestDocumentationContext.class.getName(), context);
|
||||
if (this.request.getAttribute(WriterResolver.class.getName()) == null) {
|
||||
this.request.setAttribute(WriterResolver.class.getName(),
|
||||
|
||||
@@ -29,7 +29,8 @@ public class TestRequestBuilders {
|
||||
Object... urlVariables) {
|
||||
return org.springframework.restdocs.RestDocumentationRequestBuilders.get(
|
||||
urlTemplate, urlVariables).requestAttr(
|
||||
RestDocumentationContext.class.getName(), new RestDocumentationContext());
|
||||
RestDocumentationContext.class.getName(),
|
||||
new RestDocumentationContext(null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user