Add support for post-processing a response before it is documented
This commit adds support for post-processing a response before it is
documented. Post-processors are provided to pretty-print JSON and XML
responses, remove headers from the response, and mask the hrefs of
Atom- and HAL-formatted links in JSON responses.
Response post-processing is configured prior to configuring the
documentation. For example,
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(modifyResponseTo(prettyPrintContent(), removeHeaders("a"),
maskLinks()).andDocument("post-processed"));
Closes gh-61
Closes gh-31
Closes gh-54
This commit is contained in:
@@ -59,13 +59,13 @@ project(':spring-restdocs') {
|
||||
cleanEclipseJdt.onlyIf { false }
|
||||
|
||||
dependencies {
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
|
||||
compile "junit:junit:$junitVersion"
|
||||
compile "org.springframework:spring-test:$springVersion"
|
||||
compile "org.springframework:spring-web:$springVersion"
|
||||
compile "org.springframework:spring-webmvc:$springVersion"
|
||||
compile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
|
||||
jacoco "org.jacoco:org.jacoco.agent:$jacocoVersion:runtime"
|
||||
testCompile "org.springframework:spring-webmvc:$springVersion"
|
||||
testCompile "org.springframework.hateoas:spring-hateoas:$springHateoasVersion"
|
||||
testCompile "org.mockito:mockito-core:$mockitoVersion"
|
||||
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
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.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Modifies the response in an {@link MvcResult} by applying {@link ResponsePostProcessor
|
||||
* ResponsePostProcessors} to it.
|
||||
*
|
||||
* @see RestDocumentation#modifyResponseTo(ResponsePostProcessor...)
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ResponseModifier {
|
||||
|
||||
private final List<ResponsePostProcessor> postProcessors;
|
||||
|
||||
ResponseModifier(ResponsePostProcessor... postProcessors) {
|
||||
this.postProcessors = Arrays.asList(postProcessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link RestDocumentationResultHandler} that can be used to document the
|
||||
* request and modified result.
|
||||
* @param outputDir The directory to which the documentation will be written
|
||||
* @return the result handler that will produce the documentation
|
||||
*/
|
||||
public RestDocumentationResultHandler andDocument(String outputDir) {
|
||||
return new ResponseModifyingRestDocumentationResultHandler(outputDir);
|
||||
}
|
||||
|
||||
class ResponseModifyingRestDocumentationResultHandler extends
|
||||
RestDocumentationResultHandler {
|
||||
|
||||
public ResponseModifyingRestDocumentationResultHandler(String outputDir) {
|
||||
super(outputDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws Exception {
|
||||
super.handle(postProcessResponse(result));
|
||||
}
|
||||
|
||||
MvcResult postProcessResponse(MvcResult result) throws Exception {
|
||||
MockHttpServletResponse response = result.getResponse();
|
||||
for (ResponsePostProcessor postProcessor : ResponseModifier.this.postProcessors) {
|
||||
response = postProcessor.postProcess(response);
|
||||
}
|
||||
return decorateResult(result, response);
|
||||
}
|
||||
|
||||
private MvcResult decorateResult(MvcResult result,
|
||||
MockHttpServletResponse response) {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(MvcResult.class);
|
||||
enhancer.setCallback(new GetResponseMethodInterceptor(response, result));
|
||||
return (MvcResult) enhancer.create();
|
||||
}
|
||||
|
||||
private class GetResponseMethodInterceptor implements MethodInterceptor {
|
||||
|
||||
private final MvcResult delegate;
|
||||
|
||||
private final MockHttpServletResponse response;
|
||||
|
||||
private final Method getResponseMethod = findMethod("getResponse");
|
||||
|
||||
private GetResponseMethodInterceptor(MockHttpServletResponse response,
|
||||
MvcResult delegate) {
|
||||
this.delegate = delegate;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object proxy, Method method, Object[] args,
|
||||
MethodProxy methodProxy) throws Throwable {
|
||||
if (this.getResponseMethod.equals(method)) {
|
||||
return this.response;
|
||||
}
|
||||
return method.invoke(this.delegate, args);
|
||||
}
|
||||
|
||||
private Method findMethod(String methodName) {
|
||||
return BridgeMethodResolver.findBridgedMethod(ReflectionUtils.findMethod(
|
||||
MvcResult.class, methodName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package org.springframework.restdocs;
|
||||
|
||||
import org.springframework.restdocs.response.ResponsePostProcessor;
|
||||
import org.springframework.restdocs.response.ResponsePostProcessors;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
/**
|
||||
@@ -42,4 +45,18 @@ public abstract class RestDocumentation {
|
||||
return new RestDocumentationResultHandler(outputDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the modification of the response in a {@link MvcResult} prior to it being
|
||||
* documented. The modification is performed using the given
|
||||
* {@code responsePostProcessors}.
|
||||
*
|
||||
* @param responsePostProcessors the post-processors to use to modify the response
|
||||
* @return the response modifier
|
||||
* @see ResponsePostProcessors
|
||||
*/
|
||||
public static ResponseModifier modifyResponseTo(
|
||||
ResponsePostProcessor... responsePostProcessors) {
|
||||
return new ResponseModifier(responsePostProcessors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class RestDocumentationResultHandler implements ResultHandler {
|
||||
|
||||
private final String outputDir;
|
||||
|
||||
private List<ResultHandler> delegates;
|
||||
private List<ResultHandler> delegates = new ArrayList<>();
|
||||
|
||||
RestDocumentationResultHandler(String outputDir) {
|
||||
this.outputDir = outputDir;
|
||||
@@ -60,13 +60,6 @@ public class RestDocumentationResultHandler implements ResultHandler {
|
||||
this.delegates.add(documentHttpResponse(this.outputDir));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws Exception {
|
||||
for (ResultHandler delegate : this.delegates) {
|
||||
delegate.handle(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Document the links in the response using the given {@code descriptors}. The links
|
||||
* are extracted from the response based on its content type.
|
||||
@@ -159,4 +152,12 @@ public class RestDocumentationResultHandler implements ResultHandler {
|
||||
this.delegates.add(documentQueryParameters(this.outputDir, descriptors));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws Exception {
|
||||
for (ResultHandler delegate : this.delegates) {
|
||||
delegate.handle(result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A base class for {@link ResponsePostProcessor ResponsePostProcessors} that modify the
|
||||
* content of the response.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class ContentModifyingReponsePostProcessor implements
|
||||
ResponsePostProcessor {
|
||||
|
||||
@Override
|
||||
public MockHttpServletResponse postProcess(MockHttpServletResponse response)
|
||||
throws Exception {
|
||||
String modifiedContent = modifyContent(response.getContentAsString());
|
||||
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(MockHttpServletResponse.class);
|
||||
enhancer.setCallback(new ContentModifyingMethodInterceptor(modifiedContent,
|
||||
response));
|
||||
|
||||
return (MockHttpServletResponse) enhancer.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a modified version of the given {@code originalContent}
|
||||
*
|
||||
* @param originalContent the content to modify
|
||||
* @return the modified content
|
||||
* @throws Exception if a failure occurs while modifying the content
|
||||
*/
|
||||
protected abstract String modifyContent(String originalContent) throws Exception;
|
||||
|
||||
private static class ContentModifyingMethodInterceptor implements MethodInterceptor {
|
||||
|
||||
private final Method getContentAsStringMethod = findMethod("getContentAsString");
|
||||
|
||||
private final Method getContentAsByteArray = findMethod("getContentAsByteArray");
|
||||
|
||||
private final String modifiedContent;
|
||||
|
||||
private final MockHttpServletResponse delegate;
|
||||
|
||||
public ContentModifyingMethodInterceptor(String modifiedContent,
|
||||
MockHttpServletResponse delegate) {
|
||||
this.modifiedContent = modifiedContent;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object proxy, Method method, Object[] args,
|
||||
MethodProxy methodProxy) throws Throwable {
|
||||
if (this.getContentAsStringMethod.equals(method)) {
|
||||
return this.modifiedContent;
|
||||
}
|
||||
if (this.getContentAsByteArray.equals(method)) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Following modification, the response's content should be"
|
||||
+ " accessed as a String");
|
||||
}
|
||||
return method.invoke(this.delegate, args);
|
||||
}
|
||||
|
||||
private static Method findMethod(String methodName) {
|
||||
return BridgeMethodResolver.findBridgedMethod(ReflectionUtils.findMethod(
|
||||
MockHttpServletResponse.class, methodName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A {@link ResponsePostProcessor} that removes headers from the response
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class HeaderRemovingResponsePostProcessor implements ResponsePostProcessor {
|
||||
|
||||
private final Set<String> headersToRemove;
|
||||
|
||||
public HeaderRemovingResponsePostProcessor(String... headersToRemove) {
|
||||
this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletResponse postProcess(final MockHttpServletResponse response) {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(MockHttpServletResponse.class);
|
||||
enhancer.setCallback(new HeaderHidingMethodInterceptor(this.headersToRemove,
|
||||
response));
|
||||
|
||||
return (MockHttpServletResponse) enhancer.create();
|
||||
}
|
||||
|
||||
private static final class HeaderHidingMethodInterceptor implements MethodInterceptor {
|
||||
|
||||
private final MockHttpServletResponse response;
|
||||
|
||||
private final List<Method> interceptedMethods = Arrays.asList(
|
||||
findHeaderMethod("containsHeader", String.class),
|
||||
findHeaderMethod("getHeader", String.class),
|
||||
findHeaderMethod("getHeaderValue", String.class),
|
||||
findHeaderMethod("getHeaders", String.class),
|
||||
findHeaderMethod("getHeaderValues", String.class));
|
||||
|
||||
private final Method getHeaderNamesMethod = findHeaderMethod("getHeaderNames");
|
||||
|
||||
private final Set<String> hiddenHeaders;
|
||||
|
||||
private HeaderHidingMethodInterceptor(Set<String> hiddenHeaders,
|
||||
MockHttpServletResponse response) {
|
||||
this.hiddenHeaders = hiddenHeaders;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object proxy, Method method, Object[] args,
|
||||
MethodProxy methodProxy) throws Throwable {
|
||||
if (this.getHeaderNamesMethod.equals(method)) {
|
||||
List<String> headerNames = new ArrayList<>();
|
||||
for (String candidate : this.response.getHeaderNames()) {
|
||||
if (!isHiddenHeader(candidate)) {
|
||||
headerNames.add(candidate);
|
||||
}
|
||||
}
|
||||
return headerNames;
|
||||
}
|
||||
if (this.interceptedMethods.contains(method) && isHiddenHeader(args)) {
|
||||
if (method.getReturnType().equals(boolean.class)) {
|
||||
return false;
|
||||
}
|
||||
else if (Collection.class.isAssignableFrom(method.getReturnType())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return method.invoke(this.response, args);
|
||||
}
|
||||
|
||||
private boolean isHiddenHeader(Object[] args) {
|
||||
if (args.length == 1 && args[0] instanceof String) {
|
||||
return isHiddenHeader((String) args[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isHiddenHeader(String headerName) {
|
||||
for (String hiddenHeader : this.hiddenHeaders) {
|
||||
if (hiddenHeader.equalsIgnoreCase(headerName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Method findHeaderMethod(String methodName, Class<?>... args) {
|
||||
Method candidate = ReflectionUtils.findMethod(MockHttpServletResponse.class,
|
||||
methodName, args);
|
||||
if (candidate.isBridge()) {
|
||||
return BridgeMethodResolver.findBridgedMethod(candidate);
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class LinkMaskingResponsePostProcessor extends ContentModifyingReponsePostProcessor {
|
||||
|
||||
private static final String DEFAULT_MASK = "...";
|
||||
|
||||
private static final Pattern LINK_HREF = Pattern.compile(
|
||||
"\"href\"\\s*:\\s*\"(.*?)\"", Pattern.DOTALL);
|
||||
|
||||
private final String mask;
|
||||
|
||||
LinkMaskingResponsePostProcessor() {
|
||||
this(DEFAULT_MASK);
|
||||
}
|
||||
|
||||
LinkMaskingResponsePostProcessor(String mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String modifyContent(String originalContent) {
|
||||
Matcher matcher = LINK_HREF.matcher(originalContent);
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int previous = 0;
|
||||
while (matcher.find()) {
|
||||
buffer.append(originalContent.substring(previous, matcher.start(1)));
|
||||
buffer.append(this.mask);
|
||||
previous = matcher.end(1);
|
||||
}
|
||||
if (previous < originalContent.length()) {
|
||||
buffer.append(originalContent.substring(previous));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
class PrettyPrintingResponsePostProcessor extends ContentModifyingReponsePostProcessor {
|
||||
|
||||
private static final List<PrettyPrinter> prettyPrinters = Arrays.asList(
|
||||
new JsonPrettyPrinter(), new XmlPrettyPrinter());
|
||||
|
||||
@Override
|
||||
protected String modifyContent(String originalContent) {
|
||||
if (StringUtils.hasText(originalContent)) {
|
||||
for (PrettyPrinter prettyPrinter : prettyPrinters) {
|
||||
try {
|
||||
return prettyPrinter.prettyPrint(originalContent);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return originalContent;
|
||||
}
|
||||
|
||||
private interface PrettyPrinter {
|
||||
|
||||
String prettyPrint(String string) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
private static final class XmlPrettyPrinter implements PrettyPrinter {
|
||||
|
||||
@Override
|
||||
public String prettyPrint(String original) throws Exception {
|
||||
Transformer transformer = TransformerFactory.newInstance().newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
|
||||
"4");
|
||||
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
|
||||
StringWriter transformed = new StringWriter();
|
||||
transformer.transform(new StreamSource(new StringReader(original)),
|
||||
new StreamResult(transformed));
|
||||
return transformed.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JsonPrettyPrinter implements PrettyPrinter {
|
||||
|
||||
@Override
|
||||
public String prettyPrint(String original) throws IOException {
|
||||
ObjectMapper objectMapper = new ObjectMapper().configure(
|
||||
SerializationFeature.INDENT_OUTPUT, true);
|
||||
return objectMapper.writeValueAsString(objectMapper.readTree(original));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
/**
|
||||
* A {@code ResponsePostProcessor} is used to modify the response received from a MockMvc
|
||||
* call prior to the response being documented.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public interface ResponsePostProcessor {
|
||||
|
||||
/**
|
||||
* Post-processes the given {@code response}, returning a, possibly new,
|
||||
* {@link MockHttpServletResponse} that should now be used.
|
||||
*
|
||||
* @param response The response to post-process
|
||||
* @return The result of the post-processing
|
||||
* @throws Exception if a failure occurs during the post-processing
|
||||
*/
|
||||
MockHttpServletResponse postProcess(MockHttpServletResponse response)
|
||||
throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
/**
|
||||
* Static factory methods for accessing various {@link ResponsePostProcessor
|
||||
* ResponsePostProcessors}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class ResponsePostProcessors {
|
||||
|
||||
private ResponsePostProcessors() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ResponsePostProcessor} that will pretty print the content of the
|
||||
* response.
|
||||
*
|
||||
* @return the response post-processor
|
||||
*/
|
||||
public static ResponsePostProcessor prettyPrintContent() {
|
||||
return new PrettyPrintingResponsePostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ResponsePostProcessor} that will remove the headers with the given
|
||||
* {@code headerNames} from the response.
|
||||
*
|
||||
* @param headerNames the name of the headers to remove
|
||||
* @return the response post-processor
|
||||
*/
|
||||
public static ResponsePostProcessor removeHeaders(String... headerNames) {
|
||||
return new HeaderRemovingResponsePostProcessor(headerNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ResponsePostProcessor} that will update the content of the
|
||||
* response to mask any links that it contains. Each link is masked my replacing its
|
||||
* {@code href} with {@code ...}.
|
||||
*
|
||||
* @return the response post-processor
|
||||
*/
|
||||
public static ResponsePostProcessor maskLinks() {
|
||||
return new LinkMaskingResponsePostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ResponsePostProcessor} that will update the content of the
|
||||
* response to mask any links that it contains. Each link is masked my replacing its
|
||||
* {@code href} with the given {@code mask}.
|
||||
*
|
||||
* @param mask the mask to apply
|
||||
* @return the response post-processor
|
||||
*/
|
||||
public static ResponsePostProcessor maskLinksWith(String mask) {
|
||||
return new LinkMaskingResponsePostProcessor(mask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.restdocs.test.StubMvcResult.result;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.restdocs.ResponseModifier.ResponseModifyingRestDocumentationResultHandler;
|
||||
import org.springframework.restdocs.response.ResponsePostProcessor;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResponseModifier}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public class ResponseModifierTests {
|
||||
|
||||
@Test
|
||||
public void postProcessorsAreApplied() throws Exception {
|
||||
ResponsePostProcessor first = mock(ResponsePostProcessor.class);
|
||||
ResponsePostProcessor second = mock(ResponsePostProcessor.class);
|
||||
|
||||
MockHttpServletResponse original = new MockHttpServletResponse();
|
||||
MockHttpServletResponse afterFirst = new MockHttpServletResponse();
|
||||
MockHttpServletResponse afterSecond = new MockHttpServletResponse();
|
||||
|
||||
given(first.postProcess(original)).willReturn(afterFirst);
|
||||
given(second.postProcess(afterFirst)).willReturn(afterSecond);
|
||||
|
||||
RestDocumentationResultHandler resultHandler = new ResponseModifier(first, second)
|
||||
.andDocument("test");
|
||||
assertThat(
|
||||
afterSecond,
|
||||
is(equalTo(((ResponseModifyingRestDocumentationResultHandler) resultHandler)
|
||||
.postProcessResponse(result(original)).getResponse())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,12 +16,21 @@
|
||||
|
||||
package org.springframework.restdocs;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.restdocs.RestDocumentation.document;
|
||||
import static org.springframework.restdocs.RestDocumentation.modifyResponseTo;
|
||||
import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks;
|
||||
import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent;
|
||||
import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.snippet;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -32,9 +41,13 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.restdocs.RestDocumentationIntegrationTests.TestConfiguration;
|
||||
import org.springframework.restdocs.config.RestDocumentationConfigurer;
|
||||
import org.springframework.restdocs.hypermedia.Link;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@@ -122,7 +135,39 @@ public class RestDocumentationIntegrationTests {
|
||||
assertExpectedSnippetFilesExist(
|
||||
new File("build/generated-snippets/multi-step-3/"), "http-request.adoc",
|
||||
"http-response.adoc", "curl-request.adoc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessedResponse() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk()).andDo(document("original"));
|
||||
|
||||
assertThat(
|
||||
new File("build/generated-snippets/original/http-response.adoc"),
|
||||
is(snippet().withContents(
|
||||
httpResponse(HttpStatus.OK)
|
||||
.header("a", "alpha")
|
||||
.header("Content-Type", "application/json")
|
||||
.content(
|
||||
"{\"a\":\"alpha\",\"links\":[{\"rel\":\"rel\","
|
||||
+ "\"href\":\"href\"}]}"))));
|
||||
|
||||
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(modifyResponseTo(prettyPrintContent(), removeHeaders("a"),
|
||||
maskLinks()).andDocument("post-processed"));
|
||||
|
||||
assertThat(
|
||||
new File("build/generated-snippets/post-processed/http-response.adoc"),
|
||||
is(snippet().withContents(
|
||||
httpResponse(HttpStatus.OK).header("Content-Type",
|
||||
"application/json").content(
|
||||
String.format("{%n \"a\" : \"alpha\",%n \"links\" :"
|
||||
+ " [ {%n \"rel\" : \"rel\",%n \"href\" :"
|
||||
+ " \"...\"%n } ]%n}")))));
|
||||
}
|
||||
|
||||
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
||||
@@ -146,12 +191,15 @@ public class RestDocumentationIntegrationTests {
|
||||
static class TestController {
|
||||
|
||||
@RequestMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public Map<String, String> foo() {
|
||||
Map<String, String> response = new HashMap<String, String>();
|
||||
public ResponseEntity<Map<String, Object>> foo() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("a", "alpha");
|
||||
return response;
|
||||
response.put("links", Arrays.asList(new Link("rel", "href")));
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("a", "alpha");
|
||||
return new ResponseEntity<Map<String, Object>>(response, headers,
|
||||
HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
public class ContentModifyingResponsePostProcessorTests {
|
||||
|
||||
private final MockHttpServletResponse original = new MockHttpServletResponse();
|
||||
|
||||
private final ContentModifyingReponsePostProcessor postProcessor = new TestContentModifyingResponsePostProcessor();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void contentCanBeModified() throws Exception {
|
||||
MockHttpServletResponse modified = this.postProcessor.postProcess(this.original);
|
||||
assertThat(modified.getContentAsString(), is(equalTo("modified")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonContentMethodsAreDelegated() throws Exception {
|
||||
this.original.addHeader("a", "alpha");
|
||||
MockHttpServletResponse modified = this.postProcessor.postProcess(this.original);
|
||||
assertThat(modified.getHeader("a"), is(equalTo("alpha")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getContentAsByteArrayIsUnsupported() throws Exception {
|
||||
this.thrown.expect(UnsupportedOperationException.class);
|
||||
this.thrown.expectMessage(equalTo("Following modification, the response's"
|
||||
+ " content should be accessed as a String"));
|
||||
this.postProcessor.postProcess(this.original).getContentAsByteArray();
|
||||
}
|
||||
|
||||
private static final class TestContentModifyingResponsePostProcessor extends
|
||||
ContentModifyingReponsePostProcessor {
|
||||
|
||||
@Override
|
||||
protected String modifyContent(String originalContent) throws Exception {
|
||||
return "modified";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
/**
|
||||
* Tests for {@link HeaderRemovingResponsePostProcessor}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public class HeaderRemovingResponsePostProcessorTests {
|
||||
|
||||
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
@Before
|
||||
public void configureResponse() {
|
||||
this.response.addHeader("a", "alpha");
|
||||
this.response.addHeader("b", "bravo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsHeaderHonoursRemovedHeaders() {
|
||||
MockHttpServletResponse response = removeHeaders("a");
|
||||
assertThat(response.containsHeader("a"), is(false));
|
||||
assertThat(response.containsHeader("b"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHeaderNamesHonoursRemovedHeaders() {
|
||||
MockHttpServletResponse response = removeHeaders("a");
|
||||
assertThat(response.getHeaderNames(), contains("b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHeaderHonoursRemovedHeaders() {
|
||||
MockHttpServletResponse response = removeHeaders("a");
|
||||
assertThat(response.getHeader("a"), is(nullValue()));
|
||||
assertThat(response.getHeader("b"), is("bravo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHeadersHonoursRemovedHeaders() {
|
||||
MockHttpServletResponse response = removeHeaders("a");
|
||||
assertThat(response.getHeaders("a"), is(empty()));
|
||||
assertThat(response.getHeaders("b"), contains("bravo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHeaderValueHonoursRemovedHeaders() {
|
||||
MockHttpServletResponse response = removeHeaders("a");
|
||||
assertThat(response.getHeaderValue("a"), is(nullValue()));
|
||||
assertThat(response.getHeaderValue("b"), is((Object) "bravo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHeaderValuesHonoursRemovedHeaders() {
|
||||
MockHttpServletResponse response = removeHeaders("a");
|
||||
assertThat(response.getHeaderValues("a"), is(empty()));
|
||||
assertThat(response.getHeaderValues("b"), contains((Object) "bravo"));
|
||||
}
|
||||
|
||||
private MockHttpServletResponse removeHeaders(String... headerNames) {
|
||||
return new HeaderRemovingResponsePostProcessor(headerNames)
|
||||
.postProcess(this.response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.restdocs.hypermedia.Link;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
public class LinkMaskingResponsePostProcessorTests {
|
||||
|
||||
private final LinkMaskingResponsePostProcessor postProcessor = new LinkMaskingResponsePostProcessor();
|
||||
|
||||
private final Link[] links = new Link[] { new Link("a", "alpha"),
|
||||
new Link("b", "bravo") };
|
||||
|
||||
private final Link[] maskedLinks = new Link[] { new Link("a", "..."),
|
||||
new Link("b", "...") };
|
||||
|
||||
@Test
|
||||
public void halLinksAreMasked() throws Exception {
|
||||
assertThat(this.postProcessor.modifyContent(halPayloadWithLinks(this.links)),
|
||||
is(equalTo(halPayloadWithLinks(this.maskedLinks))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formattedHalLinksAreMasked() throws Exception {
|
||||
assertThat(
|
||||
this.postProcessor
|
||||
.modifyContent(formattedHalPayloadWithLinks(this.links)),
|
||||
is(equalTo(formattedHalPayloadWithLinks(this.maskedLinks))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atomLinksAreMasked() throws Exception {
|
||||
assertThat(this.postProcessor.modifyContent(atomPayloadWithLinks(this.links)),
|
||||
is(equalTo(atomPayloadWithLinks(this.maskedLinks))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formattedAtomLinksAreMasked() throws Exception {
|
||||
assertThat(
|
||||
this.postProcessor
|
||||
.modifyContent(formattedAtomPayloadWithLinks(this.links)),
|
||||
is(equalTo(formattedAtomPayloadWithLinks(this.maskedLinks))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maskCanBeCustomized() throws Exception {
|
||||
assertThat(
|
||||
new LinkMaskingResponsePostProcessor("custom")
|
||||
.modifyContent(formattedAtomPayloadWithLinks(this.links)),
|
||||
is(equalTo(formattedAtomPayloadWithLinks(new Link("a", "custom"),
|
||||
new Link("b", "custom")))));
|
||||
}
|
||||
|
||||
private String atomPayloadWithLinks(Link... links) throws JsonProcessingException {
|
||||
return new ObjectMapper().writeValueAsString(createAtomPayload(links));
|
||||
}
|
||||
|
||||
private String formattedAtomPayloadWithLinks(Link... links)
|
||||
throws JsonProcessingException {
|
||||
return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||
.writeValueAsString(createAtomPayload(links));
|
||||
}
|
||||
|
||||
private AtomPayload createAtomPayload(Link... links) {
|
||||
AtomPayload payload = new AtomPayload();
|
||||
payload.setLinks(Arrays.asList(links));
|
||||
return payload;
|
||||
}
|
||||
|
||||
private String halPayloadWithLinks(Link... links) throws JsonProcessingException {
|
||||
return new ObjectMapper().writeValueAsString(createHalPayload(links));
|
||||
}
|
||||
|
||||
private String formattedHalPayloadWithLinks(Link... links)
|
||||
throws JsonProcessingException {
|
||||
return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||
.writeValueAsString(createHalPayload(links));
|
||||
}
|
||||
|
||||
private HalPayload createHalPayload(Link... links) {
|
||||
HalPayload payload = new HalPayload();
|
||||
Map<String, Object> linksMap = new LinkedHashMap<>();
|
||||
for (Link link : links) {
|
||||
Map<String, String> linkMap = new HashMap<>();
|
||||
linkMap.put("href", link.getHref());
|
||||
linksMap.put(link.getRel(), linkMap);
|
||||
}
|
||||
payload.setLinks(linksMap);
|
||||
return payload;
|
||||
}
|
||||
|
||||
static final class AtomPayload {
|
||||
|
||||
private List<Link> links;
|
||||
|
||||
public void setLinks(List<Link> links) {
|
||||
this.links = links;
|
||||
}
|
||||
|
||||
public List<Link> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class HalPayload {
|
||||
|
||||
private Map<String, Object> links;
|
||||
|
||||
@JsonProperty("_links")
|
||||
public Map<String, Object> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
public void setLinks(Map<String, Object> links) {
|
||||
this.links = links;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.response;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link PrettyPrintingResponsePostProcessor}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public class PrettyPrintingResponsePostProcessorTests {
|
||||
|
||||
@Test
|
||||
public void prettyPrintJson() throws Exception {
|
||||
assertThat(new PrettyPrintingResponsePostProcessor().modifyContent("{\"a\":5}"),
|
||||
equalTo(String.format("{%n \"a\" : 5%n}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prettyPrintXml() throws Exception {
|
||||
assertThat(
|
||||
new PrettyPrintingResponsePostProcessor()
|
||||
.modifyContent("<one a=\"alpha\"><two b=\"bravo\"/></one>"),
|
||||
equalTo(String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n"
|
||||
+ "<one a=\"alpha\">%n <two b=\"bravo\"/>%n</one>%n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empytContentIsHandledGracefully() throws Exception {
|
||||
assertThat(new PrettyPrintingResponsePostProcessor().modifyContent(""),
|
||||
equalTo(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonJsonAndNonXmlContentIsHandledGracefully() throws Exception {
|
||||
String content = "abcdefg";
|
||||
assertThat(new PrettyPrintingResponsePostProcessor().modifyContent(content),
|
||||
equalTo(content));
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,18 @@
|
||||
|
||||
package org.springframework.restdocs.test;
|
||||
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
|
||||
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.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.restdocs.test.SnippetMatchers.SnippetMatcher;
|
||||
|
||||
/**
|
||||
* The {@code ExpectedSnippet} rule is used to verify that a
|
||||
@@ -49,7 +41,7 @@ public class ExpectedSnippet implements TestRule {
|
||||
|
||||
private String expectedType;
|
||||
|
||||
private Matcher<String> expectedContents;
|
||||
private SnippetMatcher snippet = SnippetMatchers.snippet();
|
||||
|
||||
private File outputDir;
|
||||
|
||||
@@ -104,11 +96,7 @@ public class ExpectedSnippet implements TestRule {
|
||||
if (this.outputDir != null && this.expectedName != null) {
|
||||
File snippetDir = new File(this.outputDir, this.expectedName);
|
||||
File snippetFile = new File(snippetDir, this.expectedType + ".adoc");
|
||||
assertTrue("The file " + snippetFile + " does not exist or is not a file",
|
||||
snippetFile.isFile());
|
||||
if (this.expectedContents != null) {
|
||||
assertThat(read(snippetFile), this.expectedContents);
|
||||
}
|
||||
assertThat(snippetFile, is(this.snippet));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,45 +142,7 @@ public class ExpectedSnippet implements TestRule {
|
||||
}
|
||||
|
||||
public void withContents(Matcher<String> matcher) {
|
||||
this.expectedContents = matcher;
|
||||
}
|
||||
|
||||
private String read(File snippetFile) throws IOException {
|
||||
return FileCopyUtils.copyToString(new FileReader(snippetFile));
|
||||
}
|
||||
|
||||
public Matcher<Iterable<? extends String>> asciidoctorTableWith(String[] header,
|
||||
String[]... rows) {
|
||||
Collection<Matcher<? super String>> matchers = new ArrayList<Matcher<? super String>>();
|
||||
for (String headerItem : header) {
|
||||
matchers.add(equalTo(headerItem));
|
||||
}
|
||||
|
||||
for (String[] row : rows) {
|
||||
for (String rowItem : row) {
|
||||
matchers.add(equalTo(rowItem));
|
||||
}
|
||||
}
|
||||
|
||||
matchers.add(equalTo("|==="));
|
||||
matchers.add(equalTo(""));
|
||||
|
||||
return new IsIterableContainingInAnyOrder<String>(matchers);
|
||||
}
|
||||
|
||||
public String[] header(String... columns) {
|
||||
String header = "|"
|
||||
+ StringUtils.collectionToDelimitedString(Arrays.asList(columns), "|");
|
||||
return new String[] { "", "|===", header, "" };
|
||||
}
|
||||
|
||||
public String[] row(String... entries) {
|
||||
List<String> lines = new ArrayList<String>();
|
||||
for (String entry : entries) {
|
||||
lines.add("|" + entry);
|
||||
}
|
||||
lines.add("");
|
||||
return lines.toArray(new String[lines.size()]);
|
||||
this.snippet.withContents(matcher);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.restdocs.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -25,6 +28,7 @@ import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@@ -35,6 +39,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
*/
|
||||
public class SnippetMatchers {
|
||||
|
||||
public static SnippetMatcher snippet() {
|
||||
return new SnippetMatcher();
|
||||
}
|
||||
|
||||
public static AsciidoctorTableMatcher tableWithHeader(String... headers) {
|
||||
return new AsciidoctorTableMatcher(headers);
|
||||
}
|
||||
@@ -52,7 +60,8 @@ public class SnippetMatchers {
|
||||
return new AsciidoctorCodeBlockMatcher(language);
|
||||
}
|
||||
|
||||
private static abstract class AbstractSnippetMatcher extends BaseMatcher<String> {
|
||||
private static abstract class AbstractSnippetContentMatcher extends
|
||||
BaseMatcher<String> {
|
||||
|
||||
private List<String> lines = new ArrayList<String>();
|
||||
|
||||
@@ -99,7 +108,7 @@ public class SnippetMatchers {
|
||||
}
|
||||
|
||||
public static class AsciidoctorCodeBlockMatcher<T extends AsciidoctorCodeBlockMatcher<T>>
|
||||
extends AbstractSnippetMatcher {
|
||||
extends AbstractSnippetContentMatcher {
|
||||
|
||||
protected AsciidoctorCodeBlockMatcher(String language) {
|
||||
this.addLine("");
|
||||
@@ -152,7 +161,7 @@ public class SnippetMatchers {
|
||||
|
||||
}
|
||||
|
||||
public static class AsciidoctorTableMatcher extends AbstractSnippetMatcher {
|
||||
public static class AsciidoctorTableMatcher extends AbstractSnippetContentMatcher {
|
||||
|
||||
private AsciidoctorTableMatcher(String... columns) {
|
||||
this.addLine("");
|
||||
@@ -174,4 +183,66 @@ public class SnippetMatchers {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SnippetMatcher extends BaseMatcher<File> {
|
||||
|
||||
private Matcher<String> expectedContents;
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
if (snippetFileExists(item)) {
|
||||
if (this.expectedContents != null) {
|
||||
try {
|
||||
return this.expectedContents.matches(read((File) item));
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean snippetFileExists(Object item) {
|
||||
return item instanceof File && ((File) item).isFile();
|
||||
}
|
||||
|
||||
private String read(File snippetFile) throws IOException {
|
||||
return FileCopyUtils.copyToString(new FileReader(snippetFile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description) {
|
||||
if (!snippetFileExists(item)) {
|
||||
description.appendText("The file " + item + " does not exist");
|
||||
}
|
||||
else if (this.expectedContents != null) {
|
||||
try {
|
||||
this.expectedContents
|
||||
.describeMismatch(read((File) item), description);
|
||||
}
|
||||
catch (IOException e) {
|
||||
description.appendText("The contents of " + item
|
||||
+ " cound not be read");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
if (this.expectedContents != null) {
|
||||
this.expectedContents.describeTo(description);
|
||||
}
|
||||
else {
|
||||
description.appendText("Asciidoctor snippet");
|
||||
}
|
||||
}
|
||||
|
||||
public SnippetMatcher withContents(Matcher<String> matcher) {
|
||||
this.expectedContents = matcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ public class StubMvcResult implements MvcResult {
|
||||
return new StubMvcResult(requestBuilder);
|
||||
}
|
||||
|
||||
public static StubMvcResult result(RequestBuilder requestBuilder,
|
||||
MockHttpServletResponse response) {
|
||||
return new StubMvcResult(requestBuilder, response);
|
||||
}
|
||||
|
||||
public static StubMvcResult result(MockHttpServletRequest request) {
|
||||
return new StubMvcResult(request);
|
||||
}
|
||||
@@ -65,6 +70,10 @@ public class StubMvcResult implements MvcResult {
|
||||
this(new MockHttpServletRequest(), response);
|
||||
}
|
||||
|
||||
private StubMvcResult(RequestBuilder requestBuilder, MockHttpServletResponse response) {
|
||||
this(requestBuilder.buildRequest(new MockServletContext()), response);
|
||||
}
|
||||
|
||||
private StubMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
|
||||
Reference in New Issue
Block a user