Add support for documenting the parts of a multipart request
Closes gh-161
This commit is contained in:
@@ -527,6 +527,55 @@ above.
|
||||
|
||||
|
||||
|
||||
[[documenting-your-api-request-parts]]
|
||||
=== Request parts
|
||||
|
||||
The parts of a multipart request can be documenting using `requestParts`. For example:
|
||||
|
||||
[source,java,indent=0,role="primary"]
|
||||
.MockMvc
|
||||
----
|
||||
include::{examples-dir}/com/example/mockmvc/RequestParts.java[tags=request-parts]
|
||||
----
|
||||
<1> Perform a `POST` request with a single part named `file`.
|
||||
<2> Configure Spring REST Docs to produce a snippet describing the request's parts. Uses
|
||||
the static `requestParts` method on
|
||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||
<3> Document the part named `file`. Uses the static `partWithName` method on
|
||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.REST Assured
|
||||
----
|
||||
include::{examples-dir}/com/example/restassured/RequestParts.java[tags=request-parts]
|
||||
----
|
||||
<1> Configure Spring REST Docs to produce a snippet describing the request's parts. Uses
|
||||
the static `requestParts` method on
|
||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||
<2> Document the part named `file`. Uses the static `partWithName` method on
|
||||
`org.springframework.restdocs.request.RequestDocumentation`.
|
||||
<3> Configure the request with the part named `file`.
|
||||
<4> Perform the `POST` request to `/upload`.
|
||||
|
||||
The result is a snippet named `request-parts.adoc` that contains a table describing the
|
||||
request parts that are supported by the resource.
|
||||
|
||||
When documenting request parts, the test will fail if an undocumented part is used in the
|
||||
request. Similarly, the test will also fail if a documented part is not found in the
|
||||
request and the part has not been marked as optional.
|
||||
|
||||
Request parts can also be documented in a relaxed mode where any undocumented
|
||||
parts will not cause a test failure. To do so, use the `relaxedRequestParts` method on
|
||||
`org.springframework.restdocs.request.RequestDocumentation`. This can be useful
|
||||
when documenting a particular scenario where you only want to focus on a subset of the
|
||||
request parts.
|
||||
|
||||
If you do not want to document a request part, you can mark it as ignored. This will
|
||||
prevent it from appearing in the generated snippet while avoiding the failure described
|
||||
above.
|
||||
|
||||
|
||||
|
||||
[[documenting-your-api-http-headers]]
|
||||
=== HTTP headers
|
||||
|
||||
|
||||
41
docs/src/test/java/com/example/mockmvc/RequestParts.java
Normal file
41
docs/src/test/java/com/example/mockmvc/RequestParts.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.mockmvc;
|
||||
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
public class RequestParts {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
public void upload() throws Exception {
|
||||
// tag::request-parts[]
|
||||
this.mockMvc.perform(fileUpload("/upload").file("file", "example".getBytes())) // <1>
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("upload", requestParts( // <2>
|
||||
partWithName("file").description("The file to upload")) // <3>
|
||||
));
|
||||
// end::request-parts[]
|
||||
}
|
||||
|
||||
}
|
||||
42
docs/src/test/java/com/example/restassured/RequestParts.java
Normal file
42
docs/src/test/java/com/example/restassured/RequestParts.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.restassured;
|
||||
|
||||
import com.jayway.restassured.RestAssured;
|
||||
import com.jayway.restassured.specification.RequestSpecification;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
|
||||
|
||||
public class RequestParts {
|
||||
|
||||
private RequestSpecification spec;
|
||||
|
||||
public void upload() throws Exception {
|
||||
// tag::request-parts[]
|
||||
RestAssured.given(this.spec)
|
||||
.filter(document("users", requestParts( // <1>
|
||||
partWithName("file").description("The file to upload")))) // <2>
|
||||
.multiPart("file", "example") // <3>
|
||||
.when().post("/upload") // <4>
|
||||
.then().statusCode(is(200));
|
||||
// end::request-parts[]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,17 @@ public abstract class RequestDocumentation {
|
||||
return new ParameterDescriptor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RequestPartDescriptor} that describes a request part with the
|
||||
* given {@code name}.
|
||||
*
|
||||
* @param name The name of the request part
|
||||
* @return a {@link RequestPartDescriptor} ready for further configuration
|
||||
*/
|
||||
public static RequestPartDescriptor partWithName(String name) {
|
||||
return new RequestPartDescriptor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the path parameters from the API
|
||||
* operation's request. The parameters will be documented using the given
|
||||
@@ -207,4 +218,83 @@ public abstract class RequestDocumentation {
|
||||
return new RequestParametersSnippet(Arrays.asList(descriptors), attributes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parts from the API operation's
|
||||
* request. The parts will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a part is present in the request, but is not documented by one of the
|
||||
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a part
|
||||
* is documented, is not marked as optional, and is not present in the request, a
|
||||
* failure will also occur.
|
||||
* <p>
|
||||
* If you do not want to document a part, a part descriptor can be marked as
|
||||
* {@link RequestPartDescriptor#ignored}. This will prevent it from appearing in the
|
||||
* generated snippet while avoiding the failure described above.
|
||||
*
|
||||
* @param descriptors The descriptions of the request's parts
|
||||
* @return the snippet
|
||||
* @see OperationRequest#getParts()
|
||||
*/
|
||||
public static RequestPartsSnippet requestParts(RequestPartDescriptor... descriptors) {
|
||||
return new RequestPartsSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parts from the API operation's
|
||||
* request. The parameters will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a part is documented, is not marked as optional, and is not present in the
|
||||
* request, a failure will occur. Any undocumented parts will be ignored.
|
||||
*
|
||||
* @param descriptors The descriptions of the request's parts
|
||||
* @return the snippet
|
||||
* @see OperationRequest#getParts()
|
||||
*/
|
||||
public static RequestPartsSnippet relaxedRequestParts(
|
||||
RequestPartDescriptor... descriptors) {
|
||||
return new RequestPartsSnippet(Arrays.asList(descriptors), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parts from the API operation's
|
||||
* request. The given {@code attributes} will be available during snippet rendering
|
||||
* and the parts will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a part is present in the request, but is not documented by one of the
|
||||
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a part
|
||||
* is documented, is not marked as optional, and is not present in the request, a
|
||||
* failure will also occur.
|
||||
* <p>
|
||||
* If you do not want to document a part, a part descriptor can be marked as
|
||||
* {@link RequestPartDescriptor#ignored}. This will prevent it from appearing in the
|
||||
* generated snippet while avoiding the failure described above.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the request's parts
|
||||
* @return the snippet
|
||||
* @see OperationRequest#getParts()
|
||||
*/
|
||||
public static RequestPartsSnippet requestParts(Map<String, Object> attributes,
|
||||
RequestPartDescriptor... descriptors) {
|
||||
return new RequestPartsSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parts from the API operation's
|
||||
* request. The given {@code attributes} will be available during snippet rendering
|
||||
* and the parts will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a part is documented, is not marked as optional, and is not present in the
|
||||
* request, a failure will occur. Any undocumented parts will be ignored.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the request's parts
|
||||
* @return the snippet
|
||||
* @see OperationRequest#getParameters()
|
||||
*/
|
||||
public static RequestPartsSnippet relaxedRequestParts(Map<String, Object> attributes,
|
||||
RequestPartDescriptor... descriptors) {
|
||||
return new RequestPartsSnippet(Arrays.asList(descriptors), attributes, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 org.springframework.restdocs.snippet.IgnorableDescriptor;
|
||||
|
||||
/**
|
||||
* A descriptor of a request part.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @see RequestDocumentation#partWithName
|
||||
*/
|
||||
public class RequestPartDescriptor extends IgnorableDescriptor<RequestPartDescriptor> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private boolean optional;
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestPartDescriptor} describing the request part with the
|
||||
* given {@code name}.
|
||||
*
|
||||
* @param name the name of the request part
|
||||
*/
|
||||
protected RequestPartDescriptor(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the request part as optional.
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
public final RequestPartDescriptor optional() {
|
||||
this.optional = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the request part being described by this descriptor.
|
||||
*
|
||||
* @return the name of the parameter
|
||||
*/
|
||||
public final String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the described request part is optional, otherwise
|
||||
* {@code false}.
|
||||
*
|
||||
* @return {@code true} if the described request part is optional, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public final boolean isOptional() {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.operation.OperationRequestPart;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} that documents the request parts supported by a RESTful resource.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @see RequestDocumentation#requestParts(RequestPartDescriptor...)
|
||||
* @see RequestDocumentation#requestParts(Map, RequestPartDescriptor...)
|
||||
* @see RequestDocumentation#relaxedRequestParts(RequestPartDescriptor...)
|
||||
* @see RequestDocumentation#relaxedRequestParts(Map, RequestPartDescriptor...)
|
||||
*/
|
||||
public class RequestPartsSnippet extends TemplatedSnippet {
|
||||
|
||||
private final Map<String, RequestPartDescriptor> descriptorsByName = new LinkedHashMap<>();
|
||||
|
||||
private final boolean ignoreUndocumentedParts;
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestPartsSnippet} that will document the request's parts
|
||||
* using the given {@code descriptors}. Undocumented parts will trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
*/
|
||||
protected RequestPartsSnippet(List<RequestPartDescriptor> descriptors) {
|
||||
this(descriptors, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestPartsSnippet} that will document the request's parts
|
||||
* using the given {@code descriptors}. If {@code ignoreUndocumentedParts} is
|
||||
* {@code true}, undocumented parts will be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param ignoreUndocumentedParts whether undocumented parts should be ignored
|
||||
*/
|
||||
protected RequestPartsSnippet(List<RequestPartDescriptor> descriptors,
|
||||
boolean ignoreUndocumentedParts) {
|
||||
this(descriptors, null, ignoreUndocumentedParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestPartsSnippet} that will document the request's parts
|
||||
* using the given {@code descriptors}. The given {@code attributes} will be included
|
||||
* in the model during template rendering. Undocumented parts will trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected RequestPartsSnippet(List<RequestPartDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
this(descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestPartsSnippet} that will document the request's parts
|
||||
* using the given {@code descriptors}. The given {@code attributes} will be included
|
||||
* in the model during template rendering. If {@code ignoreUndocumentedParts} is
|
||||
* {@code true}, undocumented parts will be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @param ignoreUndocumentedParts whether undocumented parts should be ignored
|
||||
*/
|
||||
protected RequestPartsSnippet(List<RequestPartDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedParts) {
|
||||
super("request-parts", attributes);
|
||||
for (RequestPartDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getName(),
|
||||
"Request part descriptors must have a name");
|
||||
if (!descriptor.isIgnored()) {
|
||||
Assert.notNull(descriptor.getDescription(),
|
||||
"The descriptor for request part '" + descriptor.getName()
|
||||
+ "' must either have a description or be marked as "
|
||||
+ "ignored");
|
||||
}
|
||||
this.descriptorsByName.put(descriptor.getName(), descriptor);
|
||||
}
|
||||
this.ignoreUndocumentedParts = ignoreUndocumentedParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code RequestPartsSnippet} configured with this snippet's attributes
|
||||
* and its descriptors combined with the given {@code additionalDescriptors}.
|
||||
*
|
||||
* @param additionalDescriptors the additional descriptors
|
||||
* @return the new snippet
|
||||
*/
|
||||
public RequestPartsSnippet and(RequestPartDescriptor... additionalDescriptors) {
|
||||
List<RequestPartDescriptor> combinedDescriptors = new ArrayList<>();
|
||||
combinedDescriptors.addAll(this.descriptorsByName.values());
|
||||
combinedDescriptors.addAll(Arrays.asList(additionalDescriptors));
|
||||
return new RequestPartsSnippet(combinedDescriptors, this.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> createModel(Operation operation) {
|
||||
verifyRequestPartDescriptors(operation);
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
List<Map<String, Object>> requestParts = new ArrayList<>();
|
||||
for (Entry<String, RequestPartDescriptor> entry : this.descriptorsByName
|
||||
.entrySet()) {
|
||||
RequestPartDescriptor descriptor = entry.getValue();
|
||||
if (!descriptor.isIgnored()) {
|
||||
requestParts.add(createModelForDescriptor(descriptor));
|
||||
}
|
||||
}
|
||||
model.put("requestParts", requestParts);
|
||||
return model;
|
||||
}
|
||||
|
||||
private void verifyRequestPartDescriptors(Operation operation) {
|
||||
Set<String> actualRequestParts = extractActualRequestParts(operation);
|
||||
Set<String> expectedRequestParts = new HashSet<>();
|
||||
for (Entry<String, RequestPartDescriptor> entry : this.descriptorsByName
|
||||
.entrySet()) {
|
||||
if (!entry.getValue().isOptional()) {
|
||||
expectedRequestParts.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
Set<String> undocumentedRequestParts;
|
||||
if (this.ignoreUndocumentedParts) {
|
||||
undocumentedRequestParts = Collections.emptySet();
|
||||
}
|
||||
else {
|
||||
undocumentedRequestParts = new HashSet<>(actualRequestParts);
|
||||
undocumentedRequestParts.removeAll(this.descriptorsByName.keySet());
|
||||
}
|
||||
|
||||
Set<String> missingRequestParts = new HashSet<>(expectedRequestParts);
|
||||
missingRequestParts.removeAll(actualRequestParts);
|
||||
|
||||
if (!undocumentedRequestParts.isEmpty() || !missingRequestParts.isEmpty()) {
|
||||
verificationFailed(undocumentedRequestParts, missingRequestParts);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> extractActualRequestParts(Operation operation) {
|
||||
Set<String> actualRequestParts = new HashSet<>();
|
||||
for (OperationRequestPart requestPart : operation.getRequest().getParts()) {
|
||||
actualRequestParts.add(requestPart.getName());
|
||||
}
|
||||
return actualRequestParts;
|
||||
}
|
||||
|
||||
private void verificationFailed(Set<String> undocumentedRequestParts,
|
||||
Set<String> missingRequestParts) {
|
||||
String message = "";
|
||||
if (!undocumentedRequestParts.isEmpty()) {
|
||||
message += "Request parts with the following names were not documented: "
|
||||
+ undocumentedRequestParts;
|
||||
}
|
||||
if (!missingRequestParts.isEmpty()) {
|
||||
if (message.length() > 0) {
|
||||
message += ". ";
|
||||
}
|
||||
message += "Request parts with the following names were not found in "
|
||||
+ "the request: " + missingRequestParts;
|
||||
}
|
||||
throw new SnippetException(message);
|
||||
}
|
||||
|
||||
private Map<String, Object> createModelForDescriptor(
|
||||
RequestPartDescriptor descriptor) {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("name", descriptor.getName());
|
||||
model.put("description", descriptor.getDescription());
|
||||
model.put("optional", descriptor.isOptional());
|
||||
model.putAll(descriptor.getAttributes());
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|===
|
||||
|Part|Description
|
||||
|
||||
{{#requestParts}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -0,0 +1,5 @@
|
||||
Part | Description
|
||||
---- | -----------
|
||||
{{#requestParts}}
|
||||
`{{name}}` | {{description}}
|
||||
{{/requestParts}}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.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.restdocs.snippet.SnippetException;
|
||||
import org.springframework.restdocs.templates.TemplateFormats;
|
||||
import org.springframework.restdocs.test.ExpectedSnippet;
|
||||
import org.springframework.restdocs.test.OperationBuilder;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
|
||||
/**
|
||||
* Tests for failures when rendering {@link RequestPartsSnippet} due to missing or
|
||||
* undocumented request parts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class RequestPartsSnippetFailureTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedSnippet snippet = new ExpectedSnippet(TemplateFormats.asciidoctor());
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void undocumentedPart() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown.expectMessage(equalTo(
|
||||
"Request parts with the following names were" + " not documented: [a]"));
|
||||
new RequestPartsSnippet(Collections.<RequestPartDescriptor>emptyList())
|
||||
.document(new OperationBuilder("undocumented-part",
|
||||
this.snippet.getOutputDirectory()).request("http://localhost")
|
||||
.part("a", "alpha".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingPart() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown.expectMessage(equalTo("Request parts with the following names were"
|
||||
+ " not found in the request: [a]"));
|
||||
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
|
||||
.document(new OperationBuilder("missing-part",
|
||||
this.snippet.getOutputDirectory()).request("http://localhost")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedAndMissingParts() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown.expectMessage(equalTo("Request parts with the following names were"
|
||||
+ " not documented: [b]. Request parts with the following"
|
||||
+ " names were not found in the request: [a]"));
|
||||
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one")))
|
||||
.document(new OperationBuilder("undocumented-and-missing-parts",
|
||||
this.snippet.getOutputDirectory()).request("http://localhost")
|
||||
.part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.restdocs.AbstractSnippetTests;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateFormat;
|
||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
|
||||
/**
|
||||
* Tests for {@link RequestPartsSnippet}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class RequestPartsSnippetTests extends AbstractSnippetTests {
|
||||
|
||||
public RequestPartsSnippetTests(String name, TemplateFormat templateFormat) {
|
||||
super(name, templateFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestParts() throws IOException {
|
||||
this.snippet.expectRequestParts("request-parts")
|
||||
.withContents(tableWithHeader("Part", "Description").row("`a`", "one")
|
||||
.row("`b`", "two"));
|
||||
new RequestPartsSnippet(Arrays.asList(partWithName("a").description("one"),
|
||||
partWithName("b").description("two")))
|
||||
.document(operationBuilder("request-parts")
|
||||
.request("http://localhost").part("a", "bravo".getBytes())
|
||||
.and().part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoredRequestPart() throws IOException {
|
||||
this.snippet.expectRequestParts("ignored-request-part")
|
||||
.withContents(tableWithHeader("Part", "Description").row("`b`", "two"));
|
||||
new RequestPartsSnippet(Arrays.asList(partWithName("a").ignored(),
|
||||
partWithName("b").description("two")))
|
||||
.document(operationBuilder("ignored-request-part")
|
||||
.request("http://localhost").part("a", "bravo".getBytes())
|
||||
.and().part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedRequestPartsCanBeIgnored() throws IOException {
|
||||
this.snippet.expectRequestParts("ignore-all-undocumented")
|
||||
.withContents(tableWithHeader("Part", "Description").row("`b`", "two"));
|
||||
new RequestPartsSnippet(Arrays.asList(partWithName("b").description("two")), true)
|
||||
.document(operationBuilder("ignore-all-undocumented")
|
||||
.request("http://localhost").part("a", "bravo".getBytes()).and()
|
||||
.part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingOptionalRequestPart() throws IOException {
|
||||
this.snippet.expectRequestParts("missing-optional-request-parts")
|
||||
.withContents(tableWithHeader("Part", "Description").row("`a`", "one")
|
||||
.row("`b`", "two"));
|
||||
new RequestPartsSnippet(
|
||||
Arrays.asList(partWithName("a").description("one").optional(),
|
||||
partWithName("b").description("two"))).document(
|
||||
operationBuilder("missing-optional-request-parts")
|
||||
.request("http://localhost")
|
||||
.part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void presentOptionalRequestPart() throws IOException {
|
||||
this.snippet.expectRequestParts("present-optional-request-part")
|
||||
.withContents(tableWithHeader("Part", "Description").row("`a`", "one"));
|
||||
new RequestPartsSnippet(
|
||||
Arrays.asList(partWithName("a").description("one").optional()))
|
||||
.document(operationBuilder("present-optional-request-part")
|
||||
.request("http://localhost").part("a", "one".getBytes())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestPartsWithCustomAttributes() throws IOException {
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("request-parts"))
|
||||
.willReturn(snippetResource("request-parts-with-title"));
|
||||
this.snippet.expectRequestParts("request-parts-with-custom-attributes")
|
||||
.withContents(containsString("The title"));
|
||||
|
||||
new RequestPartsSnippet(
|
||||
Arrays.asList(
|
||||
partWithName("a").description("one")
|
||||
.attributes(key("foo").value("alpha")),
|
||||
partWithName("b").description("two")
|
||||
.attributes(key("foo").value("bravo"))),
|
||||
attributes(key("title").value("The title")))
|
||||
.document(operationBuilder("request-parts-with-custom-attributes")
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(resolver))
|
||||
.request("http://localhost").part("a", "alpha".getBytes())
|
||||
.and().part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestPartsWithCustomDescriptorAttributes() throws IOException {
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("request-parts"))
|
||||
.willReturn(snippetResource("request-parts-with-extra-column"));
|
||||
this.snippet.expectRequestParts("request-parts-with-custom-descriptor-attributes")
|
||||
.withContents(tableWithHeader("Part", "Description", "Foo")
|
||||
.row("a", "one", "alpha").row("b", "two", "bravo"));
|
||||
|
||||
new RequestPartsSnippet(Arrays.asList(
|
||||
partWithName("a").description("one")
|
||||
.attributes(key("foo").value("alpha")),
|
||||
partWithName("b").description("two")
|
||||
.attributes(key("foo").value("bravo"))))
|
||||
.document(operationBuilder(
|
||||
"request-parts-with-custom-descriptor-attributes")
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(
|
||||
resolver))
|
||||
.request("http://localhost")
|
||||
.part("a", "alpha".getBytes()).and()
|
||||
.part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestPartsWithOptionalColumn() throws IOException {
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("request-parts"))
|
||||
.willReturn(snippetResource("request-parts-with-optional-column"));
|
||||
this.snippet.expectRequestParts("request-parts-with-optional-column")
|
||||
.withContents(tableWithHeader("Part", "Optional", "Description")
|
||||
.row("a", "true", "one").row("b", "false", "two"));
|
||||
|
||||
new RequestPartsSnippet(
|
||||
Arrays.asList(partWithName("a").description("one").optional(),
|
||||
partWithName("b").description("two"))).document(
|
||||
operationBuilder("request-parts-with-optional-column")
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(resolver))
|
||||
.request("http://localhost")
|
||||
.part("a", "alpha".getBytes()).and()
|
||||
.part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void additionalDescriptors() throws IOException {
|
||||
this.snippet.expectRequestParts("additional-descriptors")
|
||||
.withContents(tableWithHeader("Part", "Description").row("`a`", "one")
|
||||
.row("`b`", "two"));
|
||||
RequestDocumentation.requestParts(partWithName("a").description("one"))
|
||||
.and(partWithName("b").description("two"))
|
||||
.document(operationBuilder("additional-descriptors")
|
||||
.request("http://localhost").part("a", "bravo".getBytes()).and()
|
||||
.part("b", "bravo".getBytes()).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -126,6 +126,11 @@ public class ExpectedSnippet implements TestRule {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedSnippet expectRequestParts(String name) {
|
||||
expect(name, "request-parts");
|
||||
return this;
|
||||
}
|
||||
|
||||
private ExpectedSnippet expect(String name, String type) {
|
||||
this.expectedName = name;
|
||||
this.expectedType = type;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Part|Description|Foo
|
||||
|
||||
{{#requestParts}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|{{foo}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Part|Optional|Description
|
||||
|
||||
{{#requestParts}}
|
||||
|{{name}}
|
||||
|{{optional}}
|
||||
|{{description}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
.{{title}}
|
||||
|===
|
||||
|Part|Description
|
||||
|
||||
{{#requestParts}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -0,0 +1,5 @@
|
||||
Part | Description | Foo
|
||||
---- | ----------- | ---
|
||||
{{#requestParts}}
|
||||
{{name}} | {{description}} | {{foo}}
|
||||
{{/requestParts}}
|
||||
@@ -0,0 +1,5 @@
|
||||
Part | Optional | Description
|
||||
---- | -------- | -----------
|
||||
{{#requestParts}}
|
||||
{{name}} | {{optional}} | {{description}}
|
||||
{{/requestParts}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{title}}
|
||||
Part | Description
|
||||
---- | -----------
|
||||
{{#requestParts}}
|
||||
{{name}} | {{description}}
|
||||
{{/requestParts}}
|
||||
@@ -71,8 +71,10 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
|
||||
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.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
import static org.springframework.restdocs.templates.TemplateFormats.asciidoctor;
|
||||
@@ -81,6 +83,7 @@ import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.httpRequest;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.snippet;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -259,6 +262,20 @@ public class MockMvcRestDocumentationIntegrationTests {
|
||||
"request-fields.adoc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestPartsSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(documentationConfiguration(this.restDocumentation)).build();
|
||||
|
||||
mockMvc.perform(fileUpload("/upload").file("foo", "bar".getBytes()))
|
||||
.andExpect(status().isOk()).andDo(document("request-parts", requestParts(
|
||||
partWithName("foo").description("The description"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(
|
||||
new File("build/generated-snippets/request-parts"), "http-request.adoc",
|
||||
"http-response.adoc", "curl-request.adoc", "request-parts.adoc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseFieldsSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
@@ -432,6 +449,15 @@ public class MockMvcRestDocumentationIntegrationTests {
|
||||
"$ curl 'http://localhost:8080/custom/' -i -H 'Accept: application/json'"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiPart() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(documentationConfiguration(this.restDocumentation)).build();
|
||||
mockMvc.perform(fileUpload("/upload").file("test", "content".getBytes()))
|
||||
.andExpect(status().isOk()).andDo(document("upload",
|
||||
requestParts(partWithName("test").description("Foo"))));
|
||||
}
|
||||
|
||||
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
||||
for (String snippet : snippets) {
|
||||
assertTrue(new File(directory, snippet).isFile());
|
||||
@@ -473,6 +499,11 @@ public class MockMvcRestDocumentationIntegrationTests {
|
||||
return "{\"companyName\": \"FooBar\",\"employee\": [{\"name\": \"Lorem\",\"age\": \"42\"},{\"name\": \"Ipsum\",\"age\": \"24\"}]}";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void upload() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|===
|
||||
|Request part|Description
|
||||
|
||||
{{#requestParts}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -64,8 +64,10 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
|
||||
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.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
|
||||
import static org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.modifyUris;
|
||||
@@ -185,6 +187,17 @@ public class RestAssuredRestDocumentationIntegrationTests {
|
||||
"http-response.adoc", "curl-request.adoc", "request-fields.adoc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestPartsSnippet() throws Exception {
|
||||
given().port(this.port).filter(documentationConfiguration(this.restDocumentation))
|
||||
.filter(document("request-parts",
|
||||
requestParts(partWithName("a").description("The description"))))
|
||||
.multiPart("a", "foo").post("/upload").then().statusCode(200);
|
||||
assertExpectedSnippetFilesExist(
|
||||
new File("build/generated-snippets/request-parts"), "http-request.adoc",
|
||||
"http-response.adoc", "curl-request.adoc", "request-parts.adoc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseFieldsSnippet() throws Exception {
|
||||
given().port(this.port).filter(documentationConfiguration(this.restDocumentation))
|
||||
@@ -343,6 +356,11 @@ public class RestAssuredRestDocumentationIntegrationTests {
|
||||
return "{\"companyName\": \"FooBar\",\"employee\": [{\"name\": \"Lorem\",\"age\": \"42\"},{\"name\": \"Ipsum\",\"age\": \"24\"}]}";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void upload() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user