Add support for documenting HTTP headers
This commit adds support for documenting HTTP headers in both requests and responses. Closes gh-140
This commit is contained in:
committed by
Andy Wilkinson
parent
5a010160a1
commit
48f4d1343a
@@ -73,7 +73,7 @@
|
||||
<module name="AvoidStarImport" />
|
||||
<module name="AvoidStaticImport">
|
||||
<property name="excludes"
|
||||
value="org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.curl.CurlDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
|
||||
value="org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.curl.CurlDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
|
||||
</module>
|
||||
<module name="IllegalImport" />
|
||||
<module name="RedundantImport" />
|
||||
|
||||
@@ -58,6 +58,13 @@ use of HTTP status codes.
|
||||
| The requested resource did not exist
|
||||
|===
|
||||
|
||||
[[overview-headers]]
|
||||
== Headers
|
||||
|
||||
Every response has the following header(s):
|
||||
|
||||
include::{snippets}/headers-example/response-headers.adoc[]
|
||||
|
||||
[[overview-errors]]
|
||||
== Errors
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.example.notes;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
|
||||
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
|
||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
@@ -98,6 +100,15 @@ public class ApiDocumentation {
|
||||
.alwaysDo(this.document)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersExample() throws Exception {
|
||||
this.document.snippets(responseHeaders(
|
||||
headerWithName("Content-Type").description("The Content-Type of the payload, e.g. `application/hal+json`")));
|
||||
|
||||
this.mockMvc.perform(get("/"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorExample() throws Exception {
|
||||
|
||||
@@ -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.headers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract {@link TemplatedSnippet} subclass that provides a base for snippets that
|
||||
* document a RESTful resource's request or response headers.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
*/
|
||||
public abstract class AbstractHeadersSnippet extends TemplatedSnippet {
|
||||
|
||||
private List<HeaderDescriptor> headerDescriptors;
|
||||
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Creates a new {@code AbstractHeadersSnippet} that will produce a snippet named
|
||||
* {@code <type>-headers}. The headers will be documented using the given
|
||||
* {@code descriptors} and the given {@code attributes} will be included in the model
|
||||
* during template rendering.
|
||||
*
|
||||
* @param type the type of the headers
|
||||
* @param descriptors the header descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected AbstractHeadersSnippet(String type, List<HeaderDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super(type + "-headers", attributes);
|
||||
for (HeaderDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getName());
|
||||
Assert.notNull(descriptor.getDescription());
|
||||
}
|
||||
this.headerDescriptors = descriptors;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> createModel(Operation operation) {
|
||||
validateHeaderDocumentation(operation);
|
||||
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
List<Map<String, Object>> headers = new ArrayList<>();
|
||||
model.put("headers", headers);
|
||||
for (HeaderDescriptor descriptor : this.headerDescriptors) {
|
||||
headers.add(createModelForDescriptor(descriptor));
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private void validateHeaderDocumentation(Operation operation) {
|
||||
List<HeaderDescriptor> missingHeaders = findMissingHeaders(operation);
|
||||
|
||||
if (!missingHeaders.isEmpty()) {
|
||||
String message = "";
|
||||
if (!missingHeaders.isEmpty()) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (HeaderDescriptor headerDescriptor : missingHeaders) {
|
||||
names.add(headerDescriptor.getName());
|
||||
}
|
||||
message += "Headers with the following names were not found in the "
|
||||
+ this.type + ": " + names;
|
||||
}
|
||||
throw new SnippetException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the headers that are missing from the operation. A header is missing if
|
||||
* it is described by one of the {@code headerDescriptors} but is not present in the
|
||||
* operation.
|
||||
*
|
||||
* @param operation the operation
|
||||
* @return descriptors for the headers that are missing from the operation
|
||||
*/
|
||||
protected List<HeaderDescriptor> findMissingHeaders(Operation operation) {
|
||||
List<HeaderDescriptor> missingHeaders = new ArrayList<HeaderDescriptor>();
|
||||
for (HeaderDescriptor headerDescriptor : this.headerDescriptors) {
|
||||
if (!headerDescriptor.isOptional()
|
||||
&& !getHeaders(operation).contains(headerDescriptor.getName())) {
|
||||
missingHeaders.add(headerDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return missingHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers of the request or response extracted form the given
|
||||
* {@code operation}.
|
||||
*
|
||||
* @param operation The operation
|
||||
* @return The headers
|
||||
*/
|
||||
protected abstract Set<String> getHeaders(Operation operation);
|
||||
|
||||
/**
|
||||
* Returns the list of {@link HeaderDescriptor HeaderDescriptors} that will be used to
|
||||
* generate the documentation.
|
||||
*
|
||||
* @return the header descriptors
|
||||
*/
|
||||
protected final List<HeaderDescriptor> getHeaderDescriptors() {
|
||||
return this.headerDescriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a model for the given {@code descriptor}.
|
||||
*
|
||||
* @param descriptor the descriptor
|
||||
* @return the model
|
||||
*/
|
||||
protected Map<String, Object> createModelForDescriptor(HeaderDescriptor descriptor) {
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("name", descriptor.getName());
|
||||
model.put("description", descriptor.getDescription());
|
||||
model.put("optional", descriptor.isOptional());
|
||||
model.putAll(descriptor.getAttributes());
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.headers;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.restdocs.snippet.AbstractDescriptor;
|
||||
|
||||
/**
|
||||
* A description of a header found in a request or response.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
* @see HeaderDocumentation#headerWithName(String)
|
||||
*/
|
||||
public class HeaderDescriptor extends AbstractDescriptor<HeaderDescriptor> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private boolean optional;
|
||||
|
||||
/**
|
||||
* Creates a new {@code HeaderDescriptor} describing the header with the given
|
||||
* {@code name}.
|
||||
* @param name the name
|
||||
* @see HttpHeaders
|
||||
*/
|
||||
protected HeaderDescriptor(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the header as optional.
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
public final HeaderDescriptor optional() {
|
||||
this.optional = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name for the header.
|
||||
*
|
||||
* @return the header name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the described header is optional, otherwise {@code false}.
|
||||
*
|
||||
* @return {@code true} if the described header is optional, otherwise {@code false}
|
||||
*/
|
||||
public final boolean isOptional() {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.headers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* Static factory methods for documenting a RESTful API's request and response headers.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
*/
|
||||
public abstract class HeaderDocumentation {
|
||||
|
||||
private HeaderDocumentation() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code HeaderDescriptor} that describes a header with the given
|
||||
* {@code name}.
|
||||
*
|
||||
* @param name The name of the header
|
||||
* @return a {@code HeaderDescriptor} ready for further configuration
|
||||
*/
|
||||
public static HeaderDescriptor headerWithName(String name) {
|
||||
return new HeaderDescriptor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handler that will produce a snippet documenting the headers of the API
|
||||
* call's request.
|
||||
* <p>
|
||||
* If a header is documented, is not marked as optional, and is not present in the
|
||||
* request, a failure will occur. If a header is present in the request, but is not
|
||||
* documented by one of the descriptors, there will be no failure.
|
||||
*
|
||||
* @param descriptors The descriptions of the request's headers
|
||||
* @return the handler
|
||||
* @see #headerWithName(String)
|
||||
*/
|
||||
public static Snippet requestHeaders(HeaderDescriptor... descriptors) {
|
||||
return new RequestHeadersSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handler that will produce a snippet documenting the headers of the API
|
||||
* call's request. The given {@code attributes} will be available during snippet
|
||||
* generation.
|
||||
* <p>
|
||||
* If a header is documented, is not marked as optional, and is not present in the
|
||||
* request, a failure will occur. If a header is present in the request, but is not
|
||||
* documented by one of the descriptors, there will be no failure.
|
||||
*
|
||||
* @param attributes Attributes made available during rendering of the snippet
|
||||
* @param descriptors The descriptions of the request's headers
|
||||
* @return the handler
|
||||
* @see #headerWithName(String)
|
||||
*/
|
||||
public static Snippet requestHeaders(Map<String, Object> attributes,
|
||||
HeaderDescriptor... descriptors) {
|
||||
return new RequestHeadersSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handler that will produce a snippet documenting the headers of the API
|
||||
* call's response.
|
||||
* <p>
|
||||
* If a header is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. If a header is present in the response, but is not
|
||||
* documented by one of the descriptors, there will be no failure.
|
||||
*
|
||||
* @param descriptors The descriptions of the response's headers
|
||||
* @return the handler
|
||||
* @see #headerWithName(String)
|
||||
*/
|
||||
public static Snippet responseHeaders(HeaderDescriptor... descriptors) {
|
||||
return new ResponseHeadersSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handler that will produce a snippet documenting the headers of the API
|
||||
* call's response. The given {@code attributes} will be available during snippet
|
||||
* generation.
|
||||
* <p>
|
||||
* If a header is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. If a header is present in the response, but is not
|
||||
* documented by one of the descriptors, there will be no failure.
|
||||
*
|
||||
* @param attributes Attributes made available during rendering of the snippet
|
||||
* @param descriptors The descriptions of the response's headers
|
||||
* @return the handler
|
||||
* @see #headerWithName(String)
|
||||
*/
|
||||
public static Snippet responseHeaders(Map<String, Object> attributes,
|
||||
HeaderDescriptor... descriptors) {
|
||||
return new ResponseHeadersSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.headers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} that documents the headers in a request.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
* @see HeaderDocumentation#requestHeaders(HeaderDescriptor...)
|
||||
* @see HeaderDocumentation#requestHeaders(Map, HeaderDescriptor...)
|
||||
*/
|
||||
public class RequestHeadersSnippet extends AbstractHeadersSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestHeadersSnippet} that will document the headers in the
|
||||
* request using the given {@code descriptors}.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
*/
|
||||
protected RequestHeadersSnippet(List<HeaderDescriptor> descriptors) {
|
||||
this(descriptors, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestHeadersSnippet} that will document the headers in the
|
||||
* request using the given {@code descriptors}. The given {@code attributes} will be
|
||||
* included in the model during template rendering.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected RequestHeadersSnippet(List<HeaderDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super("request", descriptors, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getHeaders(Operation operation) {
|
||||
return operation.getRequest().getHeaders().keySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.headers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} that documents the headers in a response.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
* @see HeaderDocumentation#responseHeaders(HeaderDescriptor...)
|
||||
* @see HeaderDocumentation#responseHeaders(Map, HeaderDescriptor...)
|
||||
*/
|
||||
public class ResponseHeadersSnippet extends AbstractHeadersSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResponseHeadersSnippet} that will document the headers in the
|
||||
* response using the given {@code descriptors}.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
*/
|
||||
protected ResponseHeadersSnippet(List<HeaderDescriptor> descriptors) {
|
||||
this(descriptors, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResponseHeadersSnippet} that will document the headers in the
|
||||
* response using the given {@code descriptors}. The given {@code attributes} will be
|
||||
* included in the model during template rendering.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected ResponseHeadersSnippet(List<HeaderDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super("response", descriptors, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getHeaders(Operation operation) {
|
||||
return operation.getResponse().getHeaders().keySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Documenting the headers of a RESTful API's requests and responses.
|
||||
*/
|
||||
package org.springframework.restdocs.headers;
|
||||
@@ -0,0 +1,9 @@
|
||||
|===
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -0,0 +1,9 @@
|
||||
|===
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.headers;
|
||||
|
||||
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.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;
|
||||
import org.springframework.restdocs.test.OperationBuilder;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.endsWith;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
|
||||
|
||||
/**
|
||||
* Tests for {@link RequestHeadersSnippet}.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
*/
|
||||
public class RequestHeadersSnippetTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public final ExpectedSnippet snippet = new ExpectedSnippet();
|
||||
|
||||
@Test
|
||||
public void requestWithHeaders() throws IOException {
|
||||
this.snippet.expectRequestHeaders("request-with-headers").withContents(//
|
||||
tableWithHeader("Name", "Description") //
|
||||
.row("X-Test", "one") //
|
||||
.row("Accept", "two") //
|
||||
.row("Accept-Encoding", "three") //
|
||||
.row("Accept-Language", "four") //
|
||||
.row("Cache-Control", "five") //
|
||||
.row("Connection", "six"));
|
||||
new RequestHeadersSnippet(Arrays.asList(
|
||||
headerWithName("X-Test").description("one"), //
|
||||
headerWithName("Accept").description("two"), //
|
||||
headerWithName("Accept-Encoding").description("three"), //
|
||||
headerWithName("Accept-Language").description("four"), //
|
||||
headerWithName("Cache-Control").description("five"), //
|
||||
headerWithName("Connection").description("six"))) //
|
||||
.document(new OperationBuilder("request-with-headers", this.snippet
|
||||
.getOutputDirectory()) //
|
||||
.request("http://localhost") //
|
||||
.header("X-Test", "test") //
|
||||
.header("Accept", "*/*") //
|
||||
.header("Accept-Encoding", "gzip, deflate") //
|
||||
.header("Accept-Language", "en-US,en;q=0.5") //
|
||||
.header("Cache-Control", "max-age=0") //
|
||||
.header("Connection", "keep-alive") //
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedRequestHeader() throws IOException {
|
||||
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
|
||||
"one"))).document(new OperationBuilder("undocumented-request-header",
|
||||
this.snippet.getOutputDirectory()).request("http://localhost")
|
||||
.header("X-Test", "test").header("Accept", "*/*").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingRequestHeader() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(equalTo("Headers with the following names were not found"
|
||||
+ " in the request: [Accept]"));
|
||||
new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
|
||||
"one"))).document(new OperationBuilder("missing-request-headers",
|
||||
this.snippet.getOutputDirectory()).request("http://localhost").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedRequestHeaderAndMissingRequestHeader() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(endsWith("Headers with the following names were not found"
|
||||
+ " in the request: [Accept]"));
|
||||
new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
|
||||
"one"))).document(new OperationBuilder(
|
||||
"undocumented-request-header-and-missing-request-header", this.snippet
|
||||
.getOutputDirectory()).request("http://localhost")
|
||||
.header("X-Test", "test").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestHeadersWithCustomDescriptorAttributes() throws IOException {
|
||||
this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
|
||||
.withContents(//
|
||||
tableWithHeader("Name", "Description", "Foo") //
|
||||
.row("X-Test", "one", "alpha") //
|
||||
.row("Accept-Encoding", "two", "bravo") //
|
||||
.row("Accept", "three", "charlie"));
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("request-headers")).willReturn(
|
||||
snippetResource("request-headers-with-extra-column"));
|
||||
new RequestHeadersSnippet(Arrays.asList(
|
||||
headerWithName("X-Test").description("one").attributes(
|
||||
key("foo").value("alpha")),
|
||||
headerWithName("Accept-Encoding").description("two").attributes(
|
||||
key("foo").value("bravo")),
|
||||
headerWithName("Accept").description("three").attributes(
|
||||
key("foo").value("charlie")))).document(new OperationBuilder(
|
||||
"request-headers-with-custom-attributes", this.snippet
|
||||
.getOutputDirectory())
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(resolver)).request("http://localhost")
|
||||
.header("X-Test", "test").header("Accept-Encoding", "gzip, deflate")
|
||||
.header("Accept", "*/*").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestHeadersWithCustomAttributes() throws IOException {
|
||||
this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
|
||||
.withContents(startsWith(".Custom title"));
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("request-headers")).willReturn(
|
||||
snippetResource("request-headers-with-title"));
|
||||
new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
|
||||
"one")), attributes(key("title").value("Custom title")))
|
||||
.document(new OperationBuilder("request-headers-with-custom-attributes",
|
||||
this.snippet.getOutputDirectory())
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(resolver))
|
||||
.request("http://localhost").header("X-Test", "test").build());
|
||||
}
|
||||
|
||||
private FileSystemResource snippetResource(String name) {
|
||||
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
|
||||
+ name + ".snippet");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.headers;
|
||||
|
||||
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.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;
|
||||
import org.springframework.restdocs.test.OperationBuilder;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.endsWith;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReponseHeadersSnippet}.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
*/
|
||||
public class ResponseHeadersSnippetTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public final ExpectedSnippet snippet = new ExpectedSnippet();
|
||||
|
||||
@Test
|
||||
public void responseWithHeaders() throws IOException {
|
||||
this.snippet.expectResponseHeaders("response-headers").withContents(//
|
||||
tableWithHeader("Name", "Description") //
|
||||
.row("X-Test", "one") //
|
||||
.row("Content-Type", "two") //
|
||||
.row("Etag", "three") //
|
||||
.row("Content-Length", "four") //
|
||||
.row("Cache-Control", "five") //
|
||||
.row("Vary", "six"));
|
||||
new ResponseHeadersSnippet(Arrays.asList(
|
||||
headerWithName("X-Test").description("one"), //
|
||||
headerWithName("Content-Type").description("two"), //
|
||||
headerWithName("Etag").description("three"), //
|
||||
headerWithName("Content-Length").description("four"), //
|
||||
headerWithName("Cache-Control").description("five"), //
|
||||
headerWithName("Vary").description("six"))) //
|
||||
.document(new OperationBuilder("response-headers", this.snippet
|
||||
.getOutputDirectory()) //
|
||||
.response() //
|
||||
.header("X-Test", "test") //
|
||||
.header("Content-Type", "application/json") //
|
||||
.header("Etag", "lskjadldj3ii32l2ij23") //
|
||||
.header("Content-Length", "19166") //
|
||||
.header("Cache-Control", "max-age=0") //
|
||||
.header("Vary", "User-Agent") //
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseHeadersWithCustomDescriptorAttributes() throws IOException {
|
||||
this.snippet.expectResponseHeaders("response-headers-with-custom-attributes")
|
||||
.withContents(//
|
||||
tableWithHeader("Name", "Description", "Foo") //
|
||||
.row("X-Test", "one", "alpha") //
|
||||
.row("Content-Type", "two", "bravo") //
|
||||
.row("Etag", "three", "charlie"));
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("response-headers")).willReturn(
|
||||
snippetResource("response-headers-with-extra-column"));
|
||||
new ResponseHeadersSnippet(Arrays.asList(
|
||||
headerWithName("X-Test").description("one").attributes(
|
||||
key("foo").value("alpha")),
|
||||
headerWithName("Content-Type").description("two").attributes(
|
||||
key("foo").value("bravo")),
|
||||
headerWithName("Etag").description("three").attributes(
|
||||
key("foo").value("charlie")))).document(new OperationBuilder(
|
||||
"response-headers-with-custom-attributes", this.snippet
|
||||
.getOutputDirectory())
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(resolver)).response()
|
||||
.header("X-Test", "test").header("Content-Type", "application/json")
|
||||
.header("Etag", "lskjadldj3ii32l2ij23").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseHeadersWithCustomAttributes() throws IOException {
|
||||
this.snippet.expectResponseHeaders("response-headers-with-custom-attributes")
|
||||
.withContents(startsWith(".Custom title"));
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("response-headers")).willReturn(
|
||||
snippetResource("response-headers-with-title"));
|
||||
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
|
||||
"one")), attributes(key("title").value("Custom title")))
|
||||
.document(new OperationBuilder("response-headers-with-custom-attributes",
|
||||
this.snippet.getOutputDirectory())
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(resolver)).response()
|
||||
.header("X-Test", "test").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedResponseHeader() throws IOException {
|
||||
new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
|
||||
"one"))).document(new OperationBuilder("undocumented-response-header",
|
||||
this.snippet.getOutputDirectory()).response().header("X-Test", "test")
|
||||
.header("Content-Type", "*/*").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingResponseHeader() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(equalTo("Headers with the following names were not found"
|
||||
+ " in the response: [Content-Type]"));
|
||||
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
|
||||
.description("one"))).document(new OperationBuilder(
|
||||
"missing-response-headers", this.snippet.getOutputDirectory()).response()
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedResponseHeaderAndMissingResponseHeader() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(endsWith("Headers with the following names were not found"
|
||||
+ " in the response: [Content-Type]"));
|
||||
new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
|
||||
.description("one"))).document(new OperationBuilder(
|
||||
"undocumented-response-header-and-missing-response-header", this.snippet
|
||||
.getOutputDirectory()).response().header("X-Test", "test")
|
||||
.build());
|
||||
}
|
||||
|
||||
private FileSystemResource snippetResource(String name) {
|
||||
return new FileSystemResource("src/test/resources/custom-snippet-templates/"
|
||||
+ name + ".snippet");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,6 +75,16 @@ public class ExpectedSnippet implements TestRule {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedSnippet expectRequestHeaders(String name) {
|
||||
expect(name, "request-headers");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedSnippet expectResponseHeaders(String name) {
|
||||
expect(name, "response-headers");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedSnippet expectLinks(String name) {
|
||||
expect(name, "links");
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Name|Description|Foo
|
||||
|
||||
{{#headers}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|{{foo}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
.{{title}}
|
||||
|===
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Name|Description|Foo
|
||||
|
||||
{{#headers}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|{{foo}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
.{{title}}
|
||||
|===
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
Reference in New Issue
Block a user