diff --git a/docs/src/docs/asciidoc/configuration.adoc b/docs/src/docs/asciidoc/configuration.adoc index 9654df1a..e7a0a375 100644 --- a/docs/src/docs/asciidoc/configuration.adoc +++ b/docs/src/docs/asciidoc/configuration.adoc @@ -19,9 +19,6 @@ The default configuration for URIs documented by Spring REST Docs is: |Port |`8080` - -|Context path -|Empty string |=== This configuration is applied by `RestDocumentationConfigurer`. You can use its API to @@ -32,6 +29,9 @@ change one or more of the defaults to suit your needs: include::{examples-dir}/com/example/CustomUriConfiguration.java[tags=custom-uri-configuration] ---- +TIP: To configure a request's context path, use the `contextPath` method on +`MockHttpServletRequestBuilder`. + [[configuration-snippet-encoding]] diff --git a/docs/src/test/java/com/example/CustomUriConfiguration.java b/docs/src/test/java/com/example/CustomUriConfiguration.java index 265e414b..038d5354 100644 --- a/docs/src/test/java/com/example/CustomUriConfiguration.java +++ b/docs/src/test/java/com/example/CustomUriConfiguration.java @@ -38,8 +38,7 @@ public class CustomUriConfiguration { .apply(documentationConfiguration().uris() .withScheme("https") .withHost("example.com") - .withPort(443) - .withContextPath("/api")) + .withPort(443)) .build(); // end::custom-uri-configuration[] } diff --git a/docs/src/test/java/com/example/PathParameters.java b/docs/src/test/java/com/example/PathParameters.java index 8bff0a44..1754947e 100644 --- a/docs/src/test/java/com/example/PathParameters.java +++ b/docs/src/test/java/com/example/PathParameters.java @@ -17,9 +17,9 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.test.web.servlet.MockMvc; diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/Payload.java index 1ca668b8..132568a2 100644 --- a/docs/src/test/java/com/example/Payload.java +++ b/docs/src/test/java/com/example/Payload.java @@ -17,13 +17,13 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.http.MediaType; diff --git a/docs/src/test/java/com/example/RequestParameters.java b/docs/src/test/java/com/example/RequestParameters.java index 62e88291..9155a5b9 100644 --- a/docs/src/test/java/com/example/RequestParameters.java +++ b/docs/src/test/java/com/example/RequestParameters.java @@ -17,10 +17,10 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.test.web.servlet.MockMvc; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java index 68d59555..71bca2b5 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java @@ -16,10 +16,17 @@ package org.springframework.restdocs; +import static org.springframework.restdocs.util.IterableEnumeration.iterable; + import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.springframework.restdocs.operation.MockMvcOperationRequestFactory; +import org.springframework.restdocs.operation.MockMvcOperationResponseFactory; +import org.springframework.restdocs.operation.StandardOperation; import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; @@ -44,14 +51,24 @@ public class RestDocumentationResultHandler implements ResultHandler { @Override public void handle(MvcResult result) throws Exception { + Map attributes = new HashMap<>(); + for (String name : iterable(result.getRequest().getAttributeNames())) { + attributes.put(name, result.getRequest().getAttribute(name)); + } + StandardOperation operation = new StandardOperation(this.identifier, + new MockMvcOperationRequestFactory().createOperationRequest(result + .getRequest()), + new MockMvcOperationResponseFactory().createOperationResponse(result + .getResponse()), attributes); for (Snippet snippet : getSnippets(result)) { - snippet.document(this.identifier, result); + snippet.document(operation); } } @SuppressWarnings("unchecked") private List getSnippets(MvcResult result) { - List combinedSnippets = new ArrayList<>((List) result.getRequest() + List combinedSnippets = new ArrayList<>((List) result + .getRequest() .getAttribute("org.springframework.restdocs.defaultSnippets")); combinedSnippets.addAll(this.snippets); return combinedSnippets; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java index c0fbc03e..30baa03c 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java @@ -17,7 +17,6 @@ package org.springframework.restdocs.config; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.util.StringUtils; /** * A configurer that can be used to configure the documented URIs @@ -44,20 +43,12 @@ public class UriConfigurer extends AbstractNestedConfigurer document(MvcResult result) throws IOException { + public Map createModel(Operation operation) throws IOException { Map model = new HashMap(); - model.put("arguments", getCurlCommandArguments(result)); + model.put("arguments", getCurlCommandArguments(operation)); return model; } - private String getCurlCommandArguments(MvcResult result) throws IOException { + private String getCurlCommandArguments(Operation operation) throws IOException { StringWriter command = new StringWriter(); PrintWriter printer = new PrintWriter(command); - DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( - result.getRequest()); - printer.print("'"); - writeAuthority(request, printer); - writePathAndQueryString(request, printer); + printer.print(operation.getRequest().getUri()); printer.print("'"); writeOptionToIncludeHeadersInOutput(printer); - writeHttpMethodIfNecessary(request, printer); - writeHeaders(request, printer); + writeHttpMethodIfNecessary(operation.getRequest(), printer); + writeHeaders(operation.getRequest(), printer); + writePartsIfNecessary(operation.getRequest(), printer); - if (request.isMultipartRequest()) { - writeParts(request, printer); - } - - writeContent(request, printer); + writeContent(operation.getRequest(), printer); return command.toString(); } - private void writeAuthority(DocumentableHttpServletRequest request, PrintWriter writer) { - writer.print(String.format("%s://%s", request.getScheme(), request.getHost())); - - if (isNonStandardPort(request)) { - writer.print(String.format(":%d", request.getPort())); - } - } - - private boolean isNonStandardPort(DocumentableHttpServletRequest request) { - return (SCHEME_HTTP.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTP) - || (SCHEME_HTTPS.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTPS); - } - - private void writePathAndQueryString(DocumentableHttpServletRequest request, - PrintWriter writer) { - if (StringUtils.hasText(request.getContextPath())) { - writer.print(String.format(request.getContextPath().startsWith("/") ? "%s" - : "/%s", request.getContextPath())); - } - - writer.print(request.getRequestUriWithQueryString()); - } - private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) { writer.print(" -i"); } - private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request, - PrintWriter writer) { - if (!request.isGetRequest()) { + private void writeHttpMethodIfNecessary(OperationRequest request, PrintWriter writer) { + if (!HttpMethod.GET.equals(request.getMethod())) { writer.print(String.format(" -X %s", request.getMethod())); } } - private void writeHeaders(DocumentableHttpServletRequest request, PrintWriter writer) { + private void writeHeaders(OperationRequest request, PrintWriter writer) { for (Entry> entry : request.getHeaders().entrySet()) { for (String header : entry.getValue()) { writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); @@ -127,45 +89,48 @@ class CurlRequestSnippet extends TemplatedSnippet { } } - private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer) + private void writePartsIfNecessary(OperationRequest request, PrintWriter writer) throws IOException { - for (Entry> entry : request.getMultipartFiles() - .entrySet()) { - for (MultipartFile file : entry.getValue()) { - writer.printf(" -F '%s=", file.getName()); - if (!StringUtils.hasText(file.getOriginalFilename())) { - writer.append(new String(file.getBytes())); - } - else { - writer.printf("@%s", file.getOriginalFilename()); - } - - if (StringUtils.hasText(file.getContentType())) { - writer.append(";type=").append(file.getContentType()); - } - writer.append("'"); + for (OperationRequestPart part : request.getParts()) { + writer.printf(" -F '%s=", part.getName()); + if (!StringUtils.hasText(part.getSubmittedFileName())) { + writer.append(new String(part.getContent())); + } + else { + writer.printf("@%s", part.getSubmittedFileName()); + } + if (part.getHeaders().getContentType() != null) { + writer.append(";type=").append( + part.getHeaders().getContentType().toString()); } - } + writer.append("'"); + } } - private void writeContent(DocumentableHttpServletRequest request, PrintWriter writer) + private void writeContent(OperationRequest request, PrintWriter writer) throws IOException { - if (request.getContentLength() > 0) { - writer.print(String.format(" -d '%s'", request.getContentAsString())); + if (request.getContent().length > 0) { + writer.print(String.format(" -d '%s'", new String(request.getContent()))); } - else if (request.isMultipartRequest()) { - for (Entry entry : request.getParameterMap().entrySet()) { + else if (!request.getParts().isEmpty()) { + for (Entry> entry : request.getParameters().entrySet()) { for (String value : entry.getValue()) { writer.print(String.format(" -F '%s=%s'", entry.getKey(), value)); } } } - else if (request.isPostRequest() || request.isPutRequest()) { - String queryString = request.getParameterMapAsQueryString(); + else if (isPutOrPost(request)) { + String queryString = request.getParameters().toQueryString(); if (StringUtils.hasText(queryString)) { writer.print(String.format(" -d '%s'", queryString)); } } } + + private boolean isPutOrPost(OperationRequest request) { + return HttpMethod.PUT.equals(request.getMethod()) + || HttpMethod.POST.equals(request.getMethod()); + } + } \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java index 7ff39756..fd30849a 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java @@ -26,13 +26,14 @@ import java.util.Map; import java.util.Map.Entry; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.restdocs.snippet.DocumentableHttpServletRequest; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; /** * A {@link Snippet} that documents an HTTP request. @@ -52,27 +53,31 @@ class HttpRequestSnippet extends TemplatedSnippet { } @Override - public Map document(MvcResult result) throws IOException { - DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( - result.getRequest()); + public Map createModel(Operation operation) throws IOException { Map model = new HashMap(); - model.put("method", result.getRequest().getMethod()); - model.put("path", request.getRequestUriWithQueryString()); - model.put("headers", getHeaders(request)); - model.put("requestBody", getRequestBody(request)); + model.put("method", operation.getRequest().getMethod()); + model.put( + "path", + operation.getRequest().getUri().getRawPath() + + (StringUtils.hasText(operation.getRequest().getUri() + .getRawQuery()) ? "?" + + operation.getRequest().getUri().getRawQuery() + : "")); + model.put("headers", getHeaders(operation.getRequest())); + model.put("requestBody", getRequestBody(operation.getRequest())); return model; } - private List> getHeaders(DocumentableHttpServletRequest request) { + private List> getHeaders(OperationRequest request) { List> headers = new ArrayList<>(); if (requiresHostHeader(request)) { - headers.add(header(HttpHeaders.HOST, request.getHost())); + headers.add(header(HttpHeaders.HOST, request.getUri().getHost())); } for (Entry> header : request.getHeaders().entrySet()) { for (String value : header.getValue()) { if (header.getKey() == HttpHeaders.CONTENT_TYPE - && request.isMultipartRequest()) { + && !request.getParts().isEmpty()) { headers.add(header(header.getKey(), String.format("%s; boundary=%s", value, MULTIPART_BOUNDARY))); } @@ -82,53 +87,54 @@ class HttpRequestSnippet extends TemplatedSnippet { } } - if (requiresFormEncodingContentType(request)) { + if (requiresFormEncodingContentTypeHeader(request)) { headers.add(header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)); } return headers; } - private String getRequestBody(DocumentableHttpServletRequest request) - throws IOException { + private String getRequestBody(OperationRequest request) throws IOException { StringWriter httpRequest = new StringWriter(); PrintWriter writer = new PrintWriter(httpRequest); - if (request.getContentLength() > 0) { + if (request.getContent().length > 0) { writer.println(); - writer.print(request.getContentAsString()); + writer.print(new String(request.getContent())); } - else if (request.isPostRequest() || request.isPutRequest()) { - if (request.isMultipartRequest()) { - writeParts(request, writer); - } - else { - String queryString = request.getParameterMapAsQueryString(); + else if (isPutOrPost(request)) { + if (request.getParts().isEmpty()) { + String queryString = request.getParameters().toQueryString(); if (StringUtils.hasText(queryString)) { writer.println(); writer.print(queryString); } } + else { + writeParts(request, writer); + } } return httpRequest.toString(); } - private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer) + private boolean isPutOrPost(OperationRequest request) { + return HttpMethod.PUT.equals(request.getMethod()) + || HttpMethod.POST.equals(request.getMethod()); + } + + private void writeParts(OperationRequest request, PrintWriter writer) throws IOException { writer.println(); - for (Entry parameter : request.getParameterMap().entrySet()) { + for (Entry> parameter : request.getParameters().entrySet()) { for (String value : parameter.getValue()) { writePartBoundary(writer); writePart(parameter.getKey(), value, null, writer); writer.println(); } } - for (Entry> entry : request.getMultipartFiles() - .entrySet()) { - for (MultipartFile file : entry.getValue()) { - writePartBoundary(writer); - writePart(file, writer); - writer.println(); - } + for (OperationRequestPart part : request.getParts()) { + writePartBoundary(writer); + writePart(part, writer); + writer.println(); } writeMultipartEnd(writer); } @@ -137,33 +143,33 @@ class HttpRequestSnippet extends TemplatedSnippet { writer.printf("--%s%n", MULTIPART_BOUNDARY); } - private void writePart(String name, String value, String contentType, + private void writePart(OperationRequestPart part, PrintWriter writer) + throws IOException { + writePart(part.getName(), new String(part.getContent()), part.getHeaders() + .getContentType(), writer); + } + + private void writePart(String name, String value, MediaType contentType, PrintWriter writer) { writer.printf("Content-Disposition: form-data; name=%s%n", name); - if (StringUtils.hasText(contentType)) { + if (contentType != null) { writer.printf("Content-Type: %s%n", contentType); } writer.println(); writer.print(value); } - private void writePart(MultipartFile part, PrintWriter writer) throws IOException { - writePart(part.getName(), new String(part.getBytes()), part.getContentType(), - writer); - } - private void writeMultipartEnd(PrintWriter writer) { writer.printf("--%s--", MULTIPART_BOUNDARY); } - private boolean requiresHostHeader(DocumentableHttpServletRequest request) { + private boolean requiresHostHeader(OperationRequest request) { return request.getHeaders().get(HttpHeaders.HOST) == null; } - private boolean requiresFormEncodingContentType(DocumentableHttpServletRequest request) { - return request.getHeaders().getContentType() == null - && (request.isPostRequest() || request.isPutRequest()) - && StringUtils.hasText(request.getParameterMapAsQueryString()); + private boolean requiresFormEncodingContentTypeHeader(OperationRequest request) { + return request.getHeaders().get(HttpHeaders.CONTENT_TYPE) == null + && isPutOrPost(request) && !request.getParameters().isEmpty(); } private Map header(String name, String value) { @@ -172,4 +178,5 @@ class HttpRequestSnippet extends TemplatedSnippet { header.put("value", value); return header; } + } \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java index b3dfb3c2..7f4f6b65 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java @@ -21,12 +21,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.springframework.http.HttpStatus; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.StringUtils; /** * A {@link Snippet} that documents an HTTP response. @@ -44,24 +45,26 @@ class HttpResponseSnippet extends TemplatedSnippet { } @Override - public Map document(MvcResult result) throws IOException { - HttpStatus status = HttpStatus.valueOf(result.getResponse().getStatus()); + public Map createModel(Operation operation) throws IOException { + OperationResponse response = operation.getResponse(); + HttpStatus status = response.getStatus(); Map model = new HashMap(); model.put( "responseBody", - StringUtils.hasLength(result.getResponse().getContentAsString()) ? String - .format("%n%s", result.getResponse().getContentAsString()) : ""); + response.getContent().length > 0 ? String.format("%n%s", new String( + response.getContent())) : ""); model.put("statusCode", status.value()); model.put("statusReason", status.getReasonPhrase()); - model.put("headers", headers(result)); + model.put("headers", headers(response)); return model; } - private List> headers(MvcResult result) { + private List> headers(OperationResponse response) { List> headers = new ArrayList<>(); - for (String headerName : result.getResponse().getHeaderNames()) { - for (String header : result.getResponse().getHeaders(headerName)) { - headers.add(header(headerName, header)); + for (Entry> header : response.getHeaders().entrySet()) { + List values = header.getValue(); + for (String value : values) { + headers.add(header(header.getKey(), value)); } } return headers; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java index 45c73c23..ed877ab7 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.operation.OperationResponse; import com.fasterxml.jackson.databind.ObjectMapper; @@ -35,10 +35,10 @@ abstract class AbstractJsonLinkExtractor implements LinkExtractor { @Override @SuppressWarnings("unchecked") - public Map> extractLinks(MockHttpServletResponse response) + public Map> extractLinks(OperationResponse response) throws IOException { Map jsonContent = this.objectMapper.readValue( - response.getContentAsString(), Map.class); + new String(response.getContent()), Map.class); return extractLinks(jsonContent); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java index c114d138..1c31ad3a 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java @@ -7,8 +7,7 @@ import java.util.Map; import java.util.Map.Entry; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.StringUtils; +import org.springframework.restdocs.operation.OperationResponse; /** * {@link LinkExtractor} that delegates to other link extractors based on the response's @@ -31,9 +30,9 @@ class ContentTypeLinkExtractor implements LinkExtractor { } @Override - public Map> extractLinks(MockHttpServletResponse response) + public Map> extractLinks(OperationResponse response) throws IOException { - String contentType = response.getContentType(); + MediaType contentType = response.getHeaders().getContentType(); LinkExtractor extractorForContentType = getExtractorForContentType(contentType); if (extractorForContentType != null) { return extractorForContentType.extractLinks(response); @@ -43,11 +42,10 @@ class ContentTypeLinkExtractor implements LinkExtractor { + "content type " + contentType); } - private LinkExtractor getExtractorForContentType(String contentType) { - if (StringUtils.hasText(contentType)) { - MediaType mediaType = MediaType.parseMediaType(contentType); + private LinkExtractor getExtractorForContentType(MediaType contentType) { + if (contentType != null) { for (Entry entry : this.linkExtractors.entrySet()) { - if (mediaType.isCompatibleWith(entry.getKey())) { + if (contentType.isCompatibleWith(entry.getKey())) { return entry.getValue(); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java index 0627a306..747731d6 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.operation.OperationResponse; /** * A {@code LinkExtractor} is used to extract {@link Link links} from a JSON response. The @@ -32,14 +32,13 @@ import org.springframework.mock.web.MockHttpServletResponse; public interface LinkExtractor { /** - * Extract the links from the given response, returning a {@code Map} of links where - * the keys are the link rels. + * Extract the links from the given {@code response}, returning a {@code Map} of links + * where the keys are the link rels. * * @param response The response from which the links are to be extracted * @return The extracted links, keyed by rel * @throws IOException if link extraction fails */ - Map> extractLinks(MockHttpServletResponse response) - throws IOException; + Map> extractLinks(OperationResponse response) throws IOException; } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java index 3557581b..1e01716c 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java @@ -26,10 +26,11 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.snippet.TemplatedSnippet; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; /** @@ -64,8 +65,9 @@ class LinksSnippet extends TemplatedSnippet { } @Override - protected Map document(MvcResult result) throws IOException { - validate(this.linkExtractor.extractLinks(result.getResponse())); + protected Map createModel(Operation operation) throws IOException { + OperationResponse response = operation.getResponse(); + validate(this.linkExtractor.extractLinks(response)); Map model = new HashMap<>(); model.put("links", createLinksModel()); return model; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/MockMvcOperationRequestFactory.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/MockMvcOperationRequestFactory.java new file mode 100644 index 00000000..60c82404 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/MockMvcOperationRequestFactory.java @@ -0,0 +1,162 @@ +/* + * 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.operation; + +import static org.springframework.restdocs.util.IterableEnumeration.iterable; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import javax.servlet.ServletException; +import javax.servlet.http.Part; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartHttpServletRequest; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +/** + * A factory for creating an {@link OperationRequest} from a + * {@link MockHttpServletRequest}. + * + * @author Andy Wilkinson + * + */ +public class MockMvcOperationRequestFactory { + + private static final String SCHEME_HTTP = "http"; + + private static final String SCHEME_HTTPS = "https"; + + private static final int STANDARD_PORT_HTTP = 80; + + private static final int STANDARD_PORT_HTTPS = 443; + + /** + * Creates a new {@code OperationRequest} derived from the given {@code mockRequest}. + * + * @param mockRequest the request + * @return the {@code OperationRequest} + * @throws Exception if the request could not be created + */ + public OperationRequest createOperationRequest(MockHttpServletRequest mockRequest) + throws Exception { + HttpHeaders headers = extractHeaders(mockRequest); + Parameters parameters = extractParameters(mockRequest); + List parts = extractParts(mockRequest); + String queryString = mockRequest.getQueryString(); + if (!StringUtils.hasText(queryString) && "GET".equals(mockRequest.getMethod())) { + queryString = parameters.toQueryString(); + } + return new StandardOperationRequest(URI.create(getRequestUri(mockRequest) + + (StringUtils.hasText(queryString) ? "?" + queryString : "")), + HttpMethod.valueOf(mockRequest.getMethod()), + FileCopyUtils.copyToByteArray(mockRequest.getInputStream()), headers, + parameters, parts); + } + + private List extractParts(MockHttpServletRequest servletRequest) + throws IOException, ServletException { + List parts = new ArrayList<>(); + for (Part part : servletRequest.getParts()) { + HttpHeaders partHeaders = extractHeaders(part); + List contentTypeHeader = partHeaders.get(HttpHeaders.CONTENT_TYPE); + if (part.getContentType() != null && contentTypeHeader == null) { + partHeaders + .setContentType(MediaType.parseMediaType(part.getContentType())); + } + parts.add(new StandardOperationRequestPart(part.getName(), StringUtils + .hasText(part.getSubmittedFileName()) ? part.getSubmittedFileName() + : null, FileCopyUtils.copyToByteArray(part.getInputStream()), + partHeaders)); + } + if (servletRequest instanceof MockMultipartHttpServletRequest) { + for (Entry> entry : ((MockMultipartHttpServletRequest) servletRequest) + .getMultiFileMap().entrySet()) { + for (MultipartFile file : entry.getValue()) { + HttpHeaders partHeaders = new HttpHeaders(); + if (StringUtils.hasText(file.getContentType())) { + partHeaders.setContentType(MediaType.parseMediaType(file + .getContentType())); + } + parts.add(new StandardOperationRequestPart(file.getName(), + StringUtils.hasText(file.getOriginalFilename()) ? file + .getOriginalFilename() : null, file.getBytes(), + partHeaders)); + } + } + } + return parts; + } + + private HttpHeaders extractHeaders(Part part) { + HttpHeaders partHeaders = new HttpHeaders(); + for (String headerName : part.getHeaderNames()) { + for (String value : part.getHeaders(headerName)) { + partHeaders.add(headerName, value); + } + } + return partHeaders; + } + + private Parameters extractParameters(MockHttpServletRequest servletRequest) { + Parameters parameters = new Parameters(); + for (String name : iterable(servletRequest.getParameterNames())) { + for (String value : servletRequest.getParameterValues(name)) { + parameters.add(name, value); + } + } + return parameters; + } + + private HttpHeaders extractHeaders(MockHttpServletRequest servletRequest) { + HttpHeaders headers = new HttpHeaders(); + for (String headerName : iterable(servletRequest.getHeaderNames())) { + for (String value : iterable(servletRequest.getHeaders(headerName))) { + headers.add(headerName, value); + } + } + return headers; + } + + private boolean isNonStandardPort(MockHttpServletRequest request) { + return (SCHEME_HTTP.equals(request.getScheme()) && request.getServerPort() != STANDARD_PORT_HTTP) + || (SCHEME_HTTPS.equals(request.getScheme()) && request.getServerPort() != STANDARD_PORT_HTTPS); + } + + private String getRequestUri(MockHttpServletRequest request) { + StringWriter uriWriter = new StringWriter(); + PrintWriter printer = new PrintWriter(uriWriter); + + printer.printf("%s://%s", request.getScheme(), request.getServerName()); + if (isNonStandardPort(request)) { + printer.printf(":%d", request.getServerPort()); + } + printer.print(request.getRequestURI()); + return uriWriter.toString(); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/MockMvcOperationResponseFactory.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/MockMvcOperationResponseFactory.java new file mode 100644 index 00000000..5ace0f06 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/MockMvcOperationResponseFactory.java @@ -0,0 +1,52 @@ +/* + * 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.operation; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * A factory for creating an {@link OperationResponse} derived from a + * {@link MockHttpServletResponse}. + * + * @author Andy Wilkinson + */ +public class MockMvcOperationResponseFactory { + + /** + * Create a new {@code OperationResponse} derived from the given {@code mockResponse}. + * + * @param mockResponse the response + * @return the {@code OperationResponse} + */ + public OperationResponse createOperationResponse(MockHttpServletResponse mockResponse) { + return new StandardOperationResponse( + HttpStatus.valueOf(mockResponse.getStatus()), + extractHeaders(mockResponse), mockResponse.getContentAsByteArray()); + } + + private HttpHeaders extractHeaders(MockHttpServletResponse response) { + HttpHeaders headers = new HttpHeaders(); + for (String headerName : response.getHeaderNames()) { + for (String value : response.getHeaders(headerName)) { + headers.add(headerName, value); + } + } + return headers; + } +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/Operation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/Operation.java new file mode 100644 index 00000000..fdd5fe11 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/Operation.java @@ -0,0 +1,56 @@ +/* + * 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.operation; + +import java.util.Map; + +/** + * Describes an operation performed on a RESTful service. + * + * @author Andy Wilkinson + */ +public interface Operation { + + /** + * Returns a {@code Map} of attributes associated with the operation. + * + * @return the attributes + */ + Map getAttributes(); + + /** + * Returns the name of the operation. + * + * @return the name + */ + String getName(); + + /** + * Returns the request that was sent. + * + * @return the request + */ + OperationRequest getRequest(); + + /** + * Returns the response that was received. + * + * @return the response + */ + OperationResponse getResponse(); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationRequest.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationRequest.java new file mode 100644 index 00000000..919e84ad --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationRequest.java @@ -0,0 +1,79 @@ +/* + * 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.operation; + +import java.net.URI; +import java.util.Collection; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +/** + * The request that was sent as part of performing an operation on a RESTful service. + * + * @author Andy Wilkinson + * @see Operation#getRequest() + */ +public interface OperationRequest { + + /** + * Returns the contents of the request. If the request has no content an empty array + * is returned + * + * @return the contents, never {@code null} + */ + byte[] getContent(); + + /** + * Returns the headers that were included in the request. + * + * @return the headers + */ + HttpHeaders getHeaders(); + + /** + * Returns the HTTP method of the request + * + * @return the HTTP method + */ + HttpMethod getMethod(); + + /** + * Returns the request's parameters. For a {@code GET} request, the parameters are + * derived from the query string. For a {@code POST} request, the parameters are + * derived form the request's body. + * + * @return the parameters + */ + Parameters getParameters(); + + /** + * Returns the request's parts, provided that it is a multipart request. If not, then + * an empty {@link Collection} is returned. + * + * @return the parts + */ + Collection getParts(); + + /** + * Returns the request's URI. + * + * @return the URI + */ + URI getUri(); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java new file mode 100644 index 00000000..09e9bb80 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationRequestPart.java @@ -0,0 +1,57 @@ +/* + * 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.operation; + +import org.springframework.http.HttpHeaders; + +/** + * A part of a multipart request + * + * @author awilkinson + * @see OperationRequest#getParts() + */ +public interface OperationRequestPart { + + /** + * Returns the name of the part. + * + * @return the name + */ + String getName(); + + /** + * Returns the name of the file that is being uploaded in this part. + * + * @return the name of the file + */ + String getSubmittedFileName(); + + /** + * Returns the contents of the part. + * + * @return the contents + */ + byte[] getContent(); + + /** + * Returns the part's headers. + * + * @return the headers + */ + HttpHeaders getHeaders(); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationResponse.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationResponse.java new file mode 100644 index 00000000..d7254477 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/OperationResponse.java @@ -0,0 +1,52 @@ +/* + * 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.operation; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; + +/** + * The response that was received as part of performing an operation on a RESTful service. + * + * @author Andy Wilkinson + * @see Operation + * @see Operation#getRequest() + */ +public interface OperationResponse { + + /** + * Returns the status of the response. + * + * @return the status + */ + HttpStatus getStatus(); + + /** + * Returns the headers in the response. + * + * @return the headers + */ + HttpHeaders getHeaders(); + + /** + * Returns the contents of the response. If the response has no content an empty array + * is returned. + * + * @return the contents, never {@code null} + */ + byte[] getContent(); +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/Parameters.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/Parameters.java new file mode 100644 index 00000000..d53392d1 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/Parameters.java @@ -0,0 +1,64 @@ +/* + * 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.operation; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.List; +import java.util.Map; + +import org.springframework.util.LinkedMultiValueMap; + +/** + * The parameters received in a request + * + * @author Andy Wilkinson + */ +@SuppressWarnings("serial") +public class Parameters extends LinkedMultiValueMap { + + /** + * Converts the parameters to a query string suitable for use in a URI or the body of + * a form-encoded request + * + * @return the query string + */ + public String toQueryString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry> entry : entrySet()) { + for (String value : entry.getValue()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(urlEncodeUTF8(entry.getKey())).append('=') + .append(urlEncodeUTF8(value)); + } + } + return sb.toString(); + } + + private static String urlEncodeUTF8(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } + catch (UnsupportedEncodingException ex) { + throw new IllegalStateException("Unable to URL encode " + s + " using UTF-8", + ex); + } + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperation.java new file mode 100644 index 00000000..a748df45 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperation.java @@ -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.operation; + +import java.util.Map; + +/** + * Standard implementation of {@link Operation} + * + * @author Andy Wilkinson + */ +public class StandardOperation implements Operation { + + private final String name; + + private final OperationRequest request; + + private final OperationResponse response; + + private final Map attributes; + + /** + * Creates a new {@code StandardOperation} + * + * @param name the name of the operation + * @param request the request that was sent + * @param response the response that was received + * @param attributes attributes to associate with the operation + */ + public StandardOperation(String name, OperationRequest request, + OperationResponse response, Map attributes) { + this.name = name; + this.request = request; + this.response = response; + this.attributes = attributes; + } + + @Override + public Map getAttributes() { + return this.attributes; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OperationRequest getRequest() { + return this.request; + } + + @Override + public OperationResponse getResponse() { + return this.response; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java new file mode 100644 index 00000000..37ed936f --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java @@ -0,0 +1,98 @@ +/* + * 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.operation; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +/** + * Standard implementation of {@link OperationRequest}. + * + * @author Andy Wilkinson + */ +public class StandardOperationRequest implements OperationRequest { + + private byte[] content; + + private HttpHeaders headers; + + private HttpMethod method; + + private Parameters parameters; + + private Collection parts; + + private URI uri; + + /** + * Creates a new request with the given {@code uri} and {@code method}. The request + * will have the given {@code headers}, {@code parameters}, and {@code parts}. + * + * @param uri the uri + * @param method the method + * @param content the content + * @param headers the headers + * @param parameters the parameters + * @param parts the parts + */ + public StandardOperationRequest(URI uri, HttpMethod method, byte[] content, + HttpHeaders headers, Parameters parameters, + Collection parts) { + this.uri = uri; + this.method = method; + this.content = content == null ? new byte[0] : content; + this.headers = headers; + this.parameters = parameters; + this.parts = parts; + } + + @Override + public byte[] getContent() { + return Arrays.copyOf(this.content, this.content.length); + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + + @Override + public HttpMethod getMethod() { + return this.method; + } + + @Override + public Parameters getParameters() { + return this.parameters; + } + + @Override + public Collection getParts() { + return Collections.unmodifiableCollection(this.parts); + } + + @Override + public URI getUri() { + return this.uri; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java new file mode 100644 index 00000000..cfa93d11 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationRequestPart.java @@ -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.operation; + +import org.springframework.http.HttpHeaders; + +/** + * Standard implementation of {@code OperationRequestPart}. + * + * @author Andy Wilkinson + */ +public class StandardOperationRequestPart implements OperationRequestPart { + + private final String name; + + private final String submittedFileName; + + private final byte[] content; + + private final HttpHeaders headers; + + /** + * Creates a new {@code StandardOperationRequestPart} with the given {@code name}. + * + * @param name the name of the part + * @param submittedFileName the name of the file being uploaded by this part + * @param content the contents of the part + * @param headers the headers of the part + */ + public StandardOperationRequestPart(String name, String submittedFileName, + byte[] content, HttpHeaders headers) { + this.name = name; + this.submittedFileName = submittedFileName; + this.content = content; + this.headers = headers; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getSubmittedFileName() { + return this.submittedFileName; + } + + @Override + public byte[] getContent() { + return this.content; + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java new file mode 100644 index 00000000..f7a1c594 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.operation; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; + +/** + * Standard implementation of {@link OperationResponse}. + * + * @author Andy Wilkinson + */ +public class StandardOperationResponse implements OperationResponse { + + private final HttpStatus status; + + private final HttpHeaders headers; + + private final byte[] content; + + /** + * Creates a new response with the given {@code status}, {@code headers}, and + * {@code content}. + * + * @param status the status of the response + * @param headers the headers of the response + * @param content the content of the response + */ + public StandardOperationResponse(HttpStatus status, HttpHeaders headers, + byte[] content) { + this.status = status; + this.headers = headers; + this.content = content == null ? new byte[0] : content; + } + + @Override + public HttpStatus getStatus() { + return this.status; + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + + @Override + public byte[] getContent() { + return this.content; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java index b8d4aa2d..ec93b8d1 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java @@ -17,19 +17,16 @@ package org.springframework.restdocs.payload; import java.io.IOException; -import java.io.Reader; -import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.http.MediaType; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.snippet.TemplatedSnippet; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils; /** @@ -54,22 +51,22 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { } @Override - protected Map document(MvcResult result) throws IOException { - MediaType contentType = getContentType(result); - PayloadHandler payloadHandler; + protected Map createModel(Operation operation) throws IOException { + MediaType contentType = getContentType(operation); + ContentHandler contentHandler; if (contentType != null && MediaType.APPLICATION_XML.isCompatibleWith(contentType)) { - payloadHandler = new XmlPayloadHandler(readPayload(result)); + contentHandler = new XmlContentHandler(getContent(operation)); } else { - payloadHandler = new JsonPayloadHandler(readPayload(result)); + contentHandler = new JsonContentHandler(getContent(operation)); } - validateFieldDocumentation(payloadHandler); + validateFieldDocumentation(contentHandler); for (FieldDescriptor descriptor : this.fieldDescriptors) { if (descriptor.getType() == null) { - descriptor.type(payloadHandler.determineFieldType(descriptor.getPath())); + descriptor.type(contentHandler.determineFieldType(descriptor.getPath())); } } @@ -82,17 +79,11 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { return model; } - private String readPayload(MvcResult result) throws IOException { - StringWriter writer = new StringWriter(); - FileCopyUtils.copy(getPayloadReader(result), writer); - return writer.toString(); - } - - private void validateFieldDocumentation(PayloadHandler payloadHandler) { + private void validateFieldDocumentation(ContentHandler payloadHandler) { List missingFields = payloadHandler .findMissingFields(this.fieldDescriptors); String undocumentedPayload = payloadHandler - .getUndocumentedPayload(this.fieldDescriptors); + .getUndocumentedContent(this.fieldDescriptors); if (!missingFields.isEmpty() || StringUtils.hasText(undocumentedPayload)) { String message = ""; @@ -115,8 +106,8 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { } } - protected abstract MediaType getContentType(MvcResult result); + protected abstract MediaType getContentType(Operation operation); - protected abstract Reader getPayloadReader(MvcResult result) throws IOException; + protected abstract byte[] getContent(Operation operation) throws IOException; } \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ContentHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ContentHandler.java new file mode 100644 index 00000000..0104f143 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ContentHandler.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.restdocs.payload; + +import java.util.List; + +/** + * A handler for the content of a request or response + * + * @author Andy Wilkinson + */ +interface ContentHandler { + + /** + * Finds the fields that are missing from the handler's payload. A field is missing if + * it is described by one of the {@code fieldDescriptors} but is not present in the + * payload. + * + * @param fieldDescriptors the descriptors + * @return descriptors for the fields that are missing from the payload + * @throws PayloadHandlingException if a failure occurs + */ + List findMissingFields(List fieldDescriptors); + + /** + * Returns modified content, formatted as a String, that only contains the fields that + * are undocumented. A field is undocumented if it is present in the handler's content + * but is not described by the given {@code fieldDescriptors}. If the content is + * completely documented, {@code null} is returned + * + * @param fieldDescriptors the descriptors + * @return the undocumented content, or {@code null} if all of the content is + * documented + * @throws PayloadHandlingException if a failure occurs + */ + String getUndocumentedContent(List fieldDescriptors); + + /** + * Returns the type of the field with the given {@code path} based on the content of + * the payload. + * + * @param path the field path + * @return the type of the field + */ + Object determineFieldType(String path); + +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/JsonPayloadHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java similarity index 77% rename from spring-restdocs/src/main/java/org/springframework/restdocs/payload/JsonPayloadHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java index 93c824cb..2fe9e897 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/JsonPayloadHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java @@ -10,27 +10,27 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; /** - * A {@link PayloadHandler} for JSON payloads + * A {@link ContentHandler} for JSON content * * @author Andy Wilkinson */ -class JsonPayloadHandler implements PayloadHandler { +class JsonContentHandler implements ContentHandler { private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor(); private final ObjectMapper objectMapper = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT); - private final String rawPayload; + private final byte[] rawContent; - JsonPayloadHandler(String payload) throws IOException { - this.rawPayload = payload; + JsonContentHandler(byte[] content) throws IOException { + this.rawContent = content; } @Override public List findMissingFields(List fieldDescriptors) { List missingFields = new ArrayList(); - Object payload = readPayload(); + Object payload = readContent(); for (FieldDescriptor fieldDescriptor : fieldDescriptors) { if (!fieldDescriptor.isOptional() && !this.fieldProcessor.hasField( @@ -43,15 +43,15 @@ class JsonPayloadHandler implements PayloadHandler { } @Override - public String getUndocumentedPayload(List fieldDescriptors) { - Object payload = readPayload(); + public String getUndocumentedContent(List fieldDescriptors) { + Object content = readContent(); for (FieldDescriptor fieldDescriptor : fieldDescriptors) { JsonFieldPath path = JsonFieldPath.compile(fieldDescriptor.getPath()); - this.fieldProcessor.remove(path, payload); + this.fieldProcessor.remove(path, content); } - if (!isEmpty(payload)) { + if (!isEmpty(content)) { try { - return this.objectMapper.writeValueAsString(payload); + return this.objectMapper.writeValueAsString(content); } catch (JsonProcessingException ex) { throw new PayloadHandlingException(ex); @@ -60,9 +60,9 @@ class JsonPayloadHandler implements PayloadHandler { return null; } - private Object readPayload() { + private Object readContent() { try { - return new ObjectMapper().readValue(this.rawPayload, Object.class); + return new ObjectMapper().readValue(this.rawContent, Object.class); } catch (IOException ex) { throw new PayloadHandlingException(ex); @@ -79,7 +79,7 @@ class JsonPayloadHandler implements PayloadHandler { @Override public Object determineFieldType(String path) { try { - return new JsonFieldTypeResolver().resolveFieldType(path, readPayload()); + return new JsonFieldTypeResolver().resolveFieldType(path, readContent()); } catch (FieldDoesNotExistException ex) { String message = "Cannot determine the type of the field '" + path + "' as" diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadHandler.java deleted file mode 100644 index 5d6c2d47..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.springframework.restdocs.payload; - -import java.util.List; - -/** - * A handler for a request or response payload - * - * @author Andy Wilkinson - */ -interface PayloadHandler { - - /** - * Finds the fields that are missing from the handler's payload. A field is missing if - * it is described by one of the {@code fieldDescriptors} but is not present in the - * payload. - * - * @param fieldDescriptors the descriptors - * @return descriptors for the fields that are missing from the payload - * @throws PayloadHandlingException if a failure occurs - */ - List findMissingFields(List fieldDescriptors); - - /** - * Returns a modified payload, formatted as a String, that only contains the fields - * that are undocumented. A field is undocumented if it is present in the handler's - * payload but is not described by the given {@code fieldDescriptors}. If the payload - * is completely documented, {@code null} is returned - * - * @param fieldDescriptors the descriptors - * @return the undocumented payload, or {@code null} if all of the payload is - * documented - * @throws PayloadHandlingException if a failure occurs - */ - String getUndocumentedPayload(List fieldDescriptors); - - /** - * Returns the type of the field with the given {@code path} based on the content of - * the payload. - * - * @param path the field path - * @return the type of the field - */ - Object determineFieldType(String path); - -} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java index c7e25c9b..1f72d4a3 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.restdocs.payload; import java.io.IOException; -import java.io.Reader; import java.util.List; import java.util.Map; import org.springframework.http.MediaType; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; -import org.springframework.test.web.servlet.MvcResult; /** * A {@link Snippet} that documents the fields in a request. @@ -40,17 +40,13 @@ class RequestFieldsSnippet extends AbstractFieldsSnippet { } @Override - protected Reader getPayloadReader(MvcResult result) throws IOException { - return result.getRequest().getReader(); + protected MediaType getContentType(Operation operation) { + return operation.getRequest().getHeaders().getContentType(); } @Override - protected MediaType getContentType(MvcResult result) { - String contentType = result.getRequest().getContentType(); - if (contentType != null) { - return MediaType.valueOf(contentType); - } - return null; + protected byte[] getContent(Operation operation) throws IOException { + return operation.getRequest().getContent(); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java index 440e5b71..4cd92cea 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java @@ -16,14 +16,12 @@ package org.springframework.restdocs.payload; import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; import java.util.List; import java.util.Map; import org.springframework.http.MediaType; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; -import org.springframework.test.web.servlet.MvcResult; /** * A {@link Snippet} that documents the fields in a response. @@ -42,17 +40,13 @@ class ResponseFieldsSnippet extends AbstractFieldsSnippet { } @Override - protected Reader getPayloadReader(MvcResult result) throws IOException { - return new StringReader(result.getResponse().getContentAsString()); + protected MediaType getContentType(Operation operation) { + return operation.getResponse().getHeaders().getContentType(); } @Override - protected MediaType getContentType(MvcResult result) { - String contentType = result.getResponse().getContentType(); - if (contentType != null) { - return MediaType.valueOf(contentType); - } - return null; + protected byte[] getContent(Operation operation) throws IOException { + return operation.getResponse().getContent(); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/XmlPayloadHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java similarity index 79% rename from spring-restdocs/src/main/java/org/springframework/restdocs/payload/XmlPayloadHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java index c50aaba0..53eb22e6 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/XmlPayloadHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java @@ -1,6 +1,22 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.restdocs.payload; -import java.io.StringReader; +import java.io.ByteArrayInputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; @@ -24,17 +40,17 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** - * A {@link PayloadHandler} for XML payloads + * A {@link ContentHandler} for XML content * * @author Andy Wilkinson */ -class XmlPayloadHandler implements PayloadHandler { +class XmlContentHandler implements ContentHandler { private final DocumentBuilder documentBuilder; - private final String rawPayload; + private final byte[] rawContent; - XmlPayloadHandler(String rawPayload) { + XmlContentHandler(byte[] rawContent) { try { this.documentBuilder = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); @@ -42,7 +58,7 @@ class XmlPayloadHandler implements PayloadHandler { catch (ParserConfigurationException ex) { throw new IllegalStateException("Failed to create document builder", ex); } - this.rawPayload = rawPayload; + this.rawContent = rawContent; } @Override @@ -73,8 +89,8 @@ class XmlPayloadHandler implements PayloadHandler { private Document readPayload() { try { - return this.documentBuilder.parse(new InputSource(new StringReader( - this.rawPayload))); + return this.documentBuilder.parse(new InputSource(new ByteArrayInputStream( + this.rawContent))); } catch (Exception ex) { throw new PayloadHandlingException(ex); @@ -86,7 +102,7 @@ class XmlPayloadHandler implements PayloadHandler { } @Override - public String getUndocumentedPayload(List fieldDescriptors) { + public String getUndocumentedContent(List fieldDescriptors) { Document payload = readPayload(); for (FieldDescriptor fieldDescriptor : fieldDescriptors) { NodeList matchingNodes; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java index 35a3ba2a..df0411dc 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java @@ -1,3 +1,19 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.restdocs.request; import java.io.IOException; @@ -10,12 +26,11 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.TemplatedSnippet; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; -abstract class AbstractParametersSnippet extends - TemplatedSnippet { +abstract class AbstractParametersSnippet extends TemplatedSnippet { private final Map descriptorsByName = new LinkedHashMap<>(); @@ -30,8 +45,8 @@ abstract class AbstractParametersSnippet extends } @Override - protected Map document(MvcResult result) throws IOException { - verifyParameterDescriptors(result); + protected Map createModel(Operation operation) throws IOException { + verifyParameterDescriptors(operation); Map model = new HashMap<>(); List> parameters = new ArrayList<>(); @@ -42,8 +57,8 @@ abstract class AbstractParametersSnippet extends return model; } - protected void verifyParameterDescriptors(MvcResult result) { - Set actualParameters = extractActualParameters(result); + protected void verifyParameterDescriptors(Operation operation) { + Set actualParameters = extractActualParameters(operation); Set expectedParameters = this.descriptorsByName.keySet(); Set undocumentedParameters = new HashSet(actualParameters); undocumentedParameters.removeAll(expectedParameters); @@ -58,7 +73,7 @@ abstract class AbstractParametersSnippet extends } } - protected abstract Set extractActualParameters(MvcResult result); + protected abstract Set extractActualParameters(Operation operation); protected abstract void verificationFailed(Set undocumentedParameters, Set missingParameters); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java index a8ae9d2a..99c3c765 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java @@ -24,9 +24,9 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.SnippetException; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; /** @@ -48,9 +48,9 @@ class PathParametersSnippet extends AbstractParametersSnippet { } @Override - protected Map document(MvcResult result) throws IOException { - Map model = super.document(result); - model.put("path", remoteQueryStringIfPresent(extractUrlTemplate(result))); + protected Map createModel(Operation operation) throws IOException { + Map model = super.createModel(operation); + model.put("path", remoteQueryStringIfPresent(extractUrlTemplate(operation))); return model; } @@ -63,8 +63,8 @@ class PathParametersSnippet extends AbstractParametersSnippet { } @Override - protected Set extractActualParameters(MvcResult result) { - String urlTemplate = extractUrlTemplate(result); + protected Set extractActualParameters(Operation operation) { + String urlTemplate = extractUrlTemplate(operation); Matcher matcher = NAMES_PATTERN.matcher(urlTemplate); Set actualParameters = new HashSet<>(); while (matcher.find()) { @@ -74,8 +74,8 @@ class PathParametersSnippet extends AbstractParametersSnippet { return actualParameters; } - private String extractUrlTemplate(MvcResult result) { - String urlTemplate = (String) result.getRequest().getAttribute( + private String extractUrlTemplate(Operation operation) { + String urlTemplate = (String) operation.getAttributes().get( "org.springframework.restdocs.urlTemplate"); Assert.notNull(urlTemplate, "urlTemplate not found. Did you use RestDocumentationRequestBuilders to " diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java index 1916c9b6..386fcd1a 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java @@ -22,9 +22,9 @@ import java.util.Set; import javax.servlet.ServletRequest; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.SnippetException; -import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.bind.annotation.RequestParam; /** @@ -67,8 +67,8 @@ class RequestParametersSnippet extends AbstractParametersSnippet { } @Override - protected Set extractActualParameters(MvcResult result) { - return result.getRequest().getParameterMap().keySet(); + protected Set extractActualParameters(Operation operation) { + return operation.getRequest().getParameters().keySet(); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java index 44d78999..52640b3f 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java @@ -81,9 +81,7 @@ public abstract class ContentModifyingReponsePostProcessor implements return this.modifiedContent; } if (this.getContentAsByteArray.equals(method)) { - throw new UnsupportedOperationException( - "Following modification, the response's content should be" - + " accessed as a String"); + return this.modifiedContent.getBytes(); } return method.invoke(this.delegate, args); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentableHttpServletRequest.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentableHttpServletRequest.java deleted file mode 100644 index 7c243399..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentableHttpServletRequest.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2014-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.restdocs.snippet; - -import static org.springframework.restdocs.util.IterableEnumeration.iterable; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockMultipartHttpServletRequest; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.multipart.MultipartFile; - -/** - * An {@link HttpServletRequest} wrapper that provides a limited set of methods intended - * to help in the documentation of the request. - * - * @author Andy Wilkinson - * @author Jonathan Pearlin - */ -public class DocumentableHttpServletRequest { - - private final MockHttpServletRequest delegate; - - /** - * Creates a new {@link DocumentableHttpServletRequest} to document the given - * {@code request}. - * - * @param request the request that is to be documented - */ - public DocumentableHttpServletRequest(MockHttpServletRequest request) { - this.delegate = request; - } - - /** - * Whether or not this request is a {@code GET} request. - * - * @return {@code true} if it is a {@code GET} request, otherwise {@code false} - * @see HttpServletRequest#getMethod() - */ - public boolean isGetRequest() { - return RequestMethod.GET == RequestMethod.valueOf(this.delegate.getMethod()); - } - - /** - * Whether or not this request is a {@code POST} request. - * - * @return {@code true} if it is a {@code POST} request, otherwise {@code false} - * @see HttpServletRequest#getMethod() - */ - public boolean isPostRequest() { - return RequestMethod.POST == RequestMethod.valueOf(this.delegate.getMethod()); - } - - /** - * Whether or not this request is a {@code PUT} request. - * - * @return {@code true} if it is a {@code PUT} request, otherwise {@code false} - * @see HttpServletRequest#getMethod() - */ - public boolean isPutRequest() { - return RequestMethod.PUT == RequestMethod.valueOf(this.delegate.getMethod()); - } - - /** - * Whether or not this is a multipart request. - * - * @return {@code true} if it is a multipart request, otherwise {@code false}. - * @see MockMultipartHttpServletRequest - */ - public boolean isMultipartRequest() { - return this.delegate instanceof MockMultipartHttpServletRequest; - } - - /** - * Returns a {@code Map} of the request's multipart files, or {@code null} if this - * request is not a multipart request. - * - * @return a {@code Map} of the multipart files contained in the request, or - * {@code null} - * @see #isMultipartRequest() - */ - public MultiValueMap getMultipartFiles() { - if (!isMultipartRequest()) { - return null; - } - return ((MockMultipartHttpServletRequest) this.delegate).getMultiFileMap(); - } - - /** - * Returns the request's headers. The headers are ordered based on the ordering of - * {@link HttpServletRequest#getHeaderNames()} and - * {@link HttpServletRequest#getHeaders(String)}. - * - * @return the request's headers - * @see HttpServletRequest#getHeaderNames() - * @see HttpServletRequest#getHeaders(String) - */ - public HttpHeaders getHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - for (String headerName : iterable(this.delegate.getHeaderNames())) { - for (String header : iterable(this.delegate.getHeaders(headerName))) { - httpHeaders.add(headerName, header); - } - } - return httpHeaders; - } - - /** - * Returns the request's scheme. - * - * @return the request's scheme - * @see HttpServletRequest#getScheme() - */ - public String getScheme() { - return this.delegate.getScheme(); - } - - /** - * Returns the name of the host to which the request was sent. - * - * @return the host's name - * @see HttpServletRequest#getServerName() - */ - public String getHost() { - return this.delegate.getServerName(); - } - - /** - * Returns the port to which the request was sent. - * - * @return the port - * @see HttpServletRequest#getServerPort() - */ - public int getPort() { - return this.delegate.getServerPort(); - } - - /** - * Returns the request's method. - * - * @return the request's method - * @see HttpServletRequest#getMethod() - */ - public String getMethod() { - return this.delegate.getMethod(); - } - - /** - * Returns the length of the request's content - * - * @return the content length - * @see HttpServletRequest#getContentLength() - */ - public long getContentLength() { - return this.delegate.getContentLengthLong(); - } - - /** - * Returns a {@code String} of the request's content - * - * @return the request's content - * @throws IOException if the content cannot be read - */ - public String getContentAsString() throws IOException { - StringWriter bodyWriter = new StringWriter(); - FileCopyUtils.copy(this.delegate.getReader(), bodyWriter); - return bodyWriter.toString(); - } - - /** - * Returns the request's URI including its query string. The query string is - * determined by calling {@link HttpServletRequest#getQueryString()}. If it's - * {@code null} and it is a {@code GET} request, the query string is then constructed - * from the request's {@link HttpServletRequest#getParameterMap()} parameter map. - * - * @return the URI of the request, including its query string - */ - public String getRequestUriWithQueryString() { - StringBuilder sb = new StringBuilder(); - sb.append(this.delegate.getRequestURI()); - String queryString = getQueryString(); - if (StringUtils.hasText(queryString)) { - sb.append('?').append(queryString); - } - return sb.toString(); - } - - /** - * Returns the request's parameter map formatted as a query string - * - * @return The query string derived from the request's parameter map - * @see HttpServletRequest#getParameterMap() - */ - public String getParameterMapAsQueryString() { - return toQueryString(this.delegate.getParameterMap()); - } - - /** - * Returns the request's context path - * - * @return The context path of the request - * @see HttpServletRequest#getContextPath() - */ - public String getContextPath() { - return this.delegate.getContextPath(); - } - - /** - * Returns a map of the request's parameters - * @return The map of parameters - * @see HttpServletRequest#getParameterMap() - */ - public Map getParameterMap() { - return this.delegate.getParameterMap(); - } - - private String getQueryString() { - if (this.delegate.getQueryString() != null) { - return this.delegate.getQueryString(); - } - if (isGetRequest()) { - return getParameterMapAsQueryString(); - } - return null; - } - - private static String toQueryString(Map map) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : map.entrySet()) { - for (String value : entry.getValue()) { - if (sb.length() > 0) { - sb.append("&"); - } - sb.append(urlEncodeUTF8(entry.getKey())).append('=') - .append(urlEncodeUTF8(value)); - } - } - return sb.toString(); - } - - private static String urlEncodeUTF8(String s) { - try { - return URLEncoder.encode(s, "UTF-8"); - } - catch (UnsupportedEncodingException ex) { - throw new IllegalStateException("Unable to URL encode " + s + " using UTF-8", - ex); - } - } - -} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java index 323af9e2..63438924 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java @@ -18,7 +18,7 @@ package org.springframework.restdocs.snippet; import java.io.IOException; -import org.springframework.test.web.servlet.MvcResult; +import org.springframework.restdocs.operation.Operation; /** * A {@link Snippet} is used to document aspects of a call to a RESTful API. @@ -28,13 +28,11 @@ import org.springframework.test.web.servlet.MvcResult; public interface Snippet { /** - * Documents the call to the RESTful API described by the given {@code result}. The - * call is identified by the given {@code operation}. + * Documents the call to the RESTful API described by the given {@code operation}. * * @param operation the API operation - * @param result the result of the operation - * @throws IOException if a failure occurs will documenting the result + * @throws IOException if a failure occurs will documenting the operation */ - void document(String operation, MvcResult result) throws IOException; + void document(Operation operation) throws IOException; } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java index de9f966a..7fc188bc 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java @@ -21,9 +21,9 @@ import java.io.Writer; import java.util.HashMap; import java.util.Map; +import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.templates.Template; import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.test.web.servlet.MvcResult; /** * Base class for a {@link Snippet} that is produced using a {@link Template} and @@ -45,18 +45,20 @@ public abstract class TemplatedSnippet implements Snippet { } @Override - public void document(String operation, MvcResult result) throws IOException { - WriterResolver writerResolver = (WriterResolver) result.getRequest() - .getAttribute(WriterResolver.class.getName()); - try (Writer writer = writerResolver.resolve(operation, this.snippetName)) { - Map model = document(result); + public void document(Operation operation) throws IOException { + WriterResolver writerResolver = (WriterResolver) operation.getAttributes().get( + WriterResolver.class.getName()); + try (Writer writer = writerResolver + .resolve(operation.getName(), this.snippetName)) { + Map model = createModel(operation); model.putAll(this.attributes); - TemplateEngine templateEngine = (TemplateEngine) result.getRequest() - .getAttribute(TemplateEngine.class.getName()); + TemplateEngine templateEngine = (TemplateEngine) operation.getAttributes() + .get(TemplateEngine.class.getName()); writer.append(templateEngine.compileTemplate(this.snippetName).render(model)); } } - protected abstract Map document(MvcResult result) throws IOException; + protected abstract Map createModel(Operation operation) + throws IOException; } \ No newline at end of file diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index b8344b2f..8af90689 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -91,28 +91,6 @@ public class RestDocumentationConfigurerTests { assertUriConfiguration("http", "localhost", 8081); } - @Test - public void customContextPathWithoutSlash() { - String contextPath = "context-path"; - RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() - .withContextPath(contextPath).beforeMockMvcCreated(null, null); - postProcessor.postProcessRequest(this.request); - - assertUriConfiguration("http", "localhost", 8080); - assertThat(this.request.getContextPath(), equalTo("/" + contextPath)); - } - - @Test - public void customContextPathWithSlash() { - String contextPath = "/context-path"; - RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() - .withContextPath(contextPath).beforeMockMvcCreated(null, null); - postProcessor.postProcessRequest(this.request); - - assertUriConfiguration("http", "localhost", 8080); - assertThat(this.request.getContextPath(), equalTo(contextPath)); - } - @Test public void noContentLengthHeaderWhenRequestHasNotContent() { RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java index b1cec398..48a8a566 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java @@ -19,14 +19,9 @@ package org.springframework.restdocs.curl; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.fileUpload; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.codeBlock; -import static org.springframework.restdocs.test.StubMvcResult.result; import java.io.IOException; @@ -34,13 +29,14 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockMultipartFile; 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; /** * Tests for {@link CurlRequestSnippet} @@ -62,16 +58,16 @@ public class CurlRequestSnippetTests { public void getRequest() throws IOException { this.snippet.expectCurlRequest("get-request").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo' -i")); - new CurlRequestSnippet() - .document("get-request", result(get("/foo"))); + new CurlRequestSnippet().document(new OperationBuilder("get-request").request( + "http://localhost/foo").build()); } @Test public void nonGetRequest() throws IOException { this.snippet.expectCurlRequest("non-get-request").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST")); - new CurlRequestSnippet().document("non-get-request", - result(post("/foo"))); + new CurlRequestSnippet().document(new OperationBuilder("non-get-request") + .request("http://localhost/foo").method("POST").build()); } @Test @@ -79,8 +75,8 @@ public class CurlRequestSnippetTests { this.snippet.expectCurlRequest("request-with-content").withContents( codeBlock("bash") .content("$ curl 'http://localhost/foo' -i -d 'content'")); - new CurlRequestSnippet().document("request-with-content", - result(get("/foo").content("content"))); + new CurlRequestSnippet().document(new OperationBuilder("request-with-content") + .request("http://localhost/foo").content("content").build()); } @Test @@ -89,37 +85,9 @@ public class CurlRequestSnippetTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?param=value' -i")); - new CurlRequestSnippet().document("request-with-query-string", - result(get("/foo?param=value"))); - } - - @Test - public void requestWithOneParameter() throws IOException { - this.snippet.expectCurlRequest("request-with-one-parameter").withContents( - codeBlock("bash").content("$ curl 'http://localhost/foo?k1=v1' -i")); - new CurlRequestSnippet().document("request-with-one-parameter", - result(get("/foo").param("k1", "v1"))); - } - - @Test - public void requestWithMultipleParameters() throws IOException { - this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents( - codeBlock("bash").content( - "$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i")); - new CurlRequestSnippet().document( - "request-with-multiple-parameters", result(get("/foo").param("k1", "v1") - .param("k2", "v2").param("k1", "v1-bis"))); - } - - @Test - public void requestWithUrlEncodedParameter() throws IOException { - this.snippet.expectCurlRequest("request-with-url-encoded-parameter") - .withContents( - codeBlock("bash").content( - "$ curl 'http://localhost/foo?k1=foo+bar%26' -i")); - new CurlRequestSnippet().document( - "request-with-url-encoded-parameter", - result(get("/foo").param("k1", "foo bar&"))); + new CurlRequestSnippet().document(new OperationBuilder( + "request-with-query-string").request("http://localhost/foo?param=value") + .build()); } @Test @@ -127,8 +95,9 @@ public class CurlRequestSnippetTests { this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); - new CurlRequestSnippet().document("post-request-with-one-parameter", - result(post("/foo").param("k1", "v1"))); + new CurlRequestSnippet().document(new OperationBuilder( + "post-request-with-one-parameter").request("http://localhost/foo") + .method("POST").param("k1", "v1").build()); } @Test @@ -138,9 +107,9 @@ public class CurlRequestSnippetTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); - new CurlRequestSnippet().document( - "post-request-with-multiple-parameters", - result(post("/foo").param("k1", "v1", "v1-bis").param("k2", "v2"))); + new CurlRequestSnippet().document(new OperationBuilder( + "post-request-with-multiple-parameters").request("http://localhost/foo") + .method("POST").param("k1", "v1", "v1-bis").param("k2", "v2").build()); } @Test @@ -150,9 +119,10 @@ public class CurlRequestSnippetTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); - new CurlRequestSnippet().document( - "post-request-with-url-encoded-parameter", - result(post("/foo").param("k1", "a&b"))); + new CurlRequestSnippet().document(new OperationBuilder( + "post-request-with-url-encoded-parameter") + .request("http://localhost/foo").method("POST").param("k1", "a&b") + .build()); } @Test @@ -160,8 +130,9 @@ public class CurlRequestSnippetTests { this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); - new CurlRequestSnippet().document("put-request-with-one-parameter", - result(put("/foo").param("k1", "v1"))); + new CurlRequestSnippet().document(new OperationBuilder( + "put-request-with-one-parameter").request("http://localhost/foo") + .method("PUT").param("k1", "v1").build()); } @Test @@ -171,9 +142,10 @@ public class CurlRequestSnippetTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); - new CurlRequestSnippet().document( - "put-request-with-multiple-parameters", - result(put("/foo").param("k1", "v1", "v1-bis").param("k2", "v2"))); + new CurlRequestSnippet().document(new OperationBuilder( + "put-request-with-multiple-parameters").request("http://localhost/foo") + .method("PUT").param("k1", "v1").param("k1", "v1-bis").param("k2", "v2") + .build()); } @Test @@ -182,9 +154,9 @@ public class CurlRequestSnippetTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); - new CurlRequestSnippet().document( - "put-request-with-url-encoded-parameter", - result(put("/foo").param("k1", "a&b"))); + new CurlRequestSnippet().document(new OperationBuilder( + "put-request-with-url-encoded-parameter").request("http://localhost/foo") + .method("PUT").param("k1", "a&b").build()); } @Test @@ -193,92 +165,24 @@ public class CurlRequestSnippetTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i" + " -H 'Content-Type: application/json' -H 'a: alpha'")); - new CurlRequestSnippet().document( - "request-with-headers", - result(get("/foo").contentType(MediaType.APPLICATION_JSON).header("a", - "alpha"))); + new CurlRequestSnippet().document(new OperationBuilder("request-with-headers") + .request("http://localhost/foo") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header("a", "alpha").build()); } @Test - public void httpWithNonStandardPort() throws IOException { - this.snippet.expectCurlRequest("http-with-non-standard-port").withContents( - codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerPort(8080); - new CurlRequestSnippet().document("http-with-non-standard-port", - result(request)); - } - - @Test - public void httpsWithStandardPort() throws IOException { - this.snippet.expectCurlRequest("https-with-standard-port").withContents( - codeBlock("bash").content("$ curl 'https://localhost/foo' -i")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerPort(443); - request.setScheme("https"); - new CurlRequestSnippet().document("https-with-standard-port", - result(request)); - } - - @Test - public void httpsWithNonStandardPort() throws IOException { - this.snippet.expectCurlRequest("https-with-non-standard-port").withContents( - codeBlock("bash").content("$ curl 'https://localhost:8443/foo' -i")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerPort(8443); - request.setScheme("https"); - new CurlRequestSnippet().document("https-with-non-standard-port", - result(request)); - } - - @Test - public void requestWithCustomHost() throws IOException { - this.snippet.expectCurlRequest("request-with-custom-host").withContents( - codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerName("api.example.com"); - new CurlRequestSnippet().document("request-with-custom-host", - result(request)); - } - - @Test - public void requestWithContextPathWithSlash() throws IOException { - this.snippet.expectCurlRequest("request-with-custom-context-with-slash") - .withContents( - codeBlock("bash").content( - "$ curl 'http://api.example.com/v3/foo' -i")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerName("api.example.com"); - request.setContextPath("/v3"); - new CurlRequestSnippet().document( - "request-with-custom-context-with-slash", result(request)); - } - - @Test - public void requestWithContextPathWithoutSlash() throws IOException { - this.snippet.expectCurlRequest("request-with-custom-context-without-slash") - .withContents( - codeBlock("bash").content( - "$ curl 'http://api.example.com/v3/foo' -i")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerName("api.example.com"); - request.setContextPath("v3"); - new CurlRequestSnippet().document( - "request-with-custom-context-without-slash", result(request)); - } - - @Test - public void multipartPostWithNoOriginalFilename() throws IOException { + public void multipartPostWithNoSubmittedFileName() throws IOException { String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " + "'Content-Type: multipart/form-data' -F " + "'metadata={\"description\": \"foo\"}'"; this.snippet.expectCurlRequest("multipart-post-no-original-filename") .withContents(codeBlock("bash").content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("metadata", - "{\"description\": \"foo\"}".getBytes()); - new CurlRequestSnippet().document( - "multipart-post-no-original-filename", - result(fileUpload("/upload").file(multipartFile))); + new CurlRequestSnippet().document(new OperationBuilder( + "multipart-post-no-original-filename").request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("metadata", "{\"description\": \"foo\"}".getBytes()).build()); } @Test @@ -288,12 +192,13 @@ public class CurlRequestSnippetTests { + "'image=@documents/images/example.png;type=image/png'"; this.snippet.expectCurlRequest("multipart-post-with-content-type").withContents( codeBlock("bash").content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("image", - "documents/images/example.png", MediaType.IMAGE_PNG_VALUE, - "bytes".getBytes()); - new CurlRequestSnippet().document( - "multipart-post-with-content-type", - result(fileUpload("/upload").file(multipartFile))); + new CurlRequestSnippet().document(new OperationBuilder( + "multipart-post-with-content-type").request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE) + .submittedFileName("documents/images/example.png").build()); } @Test @@ -303,10 +208,11 @@ public class CurlRequestSnippetTests { + "'image=@documents/images/example.png'"; this.snippet.expectCurlRequest("multipart-post").withContents( codeBlock("bash").content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("image", - "documents/images/example.png", null, "bytes".getBytes()); - new CurlRequestSnippet().document("multipart-post", - result(fileUpload("/upload").file(multipartFile))); + new CurlRequestSnippet().document(new OperationBuilder("multipart-post") + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .submittedFileName("documents/images/example.png").build()); } @Test @@ -315,14 +221,15 @@ public class CurlRequestSnippetTests { + "'Content-Type: multipart/form-data' -F " + "'image=@documents/images/example.png' -F 'a=apple' -F 'a=avocado' " + "-F 'b=banana'"; - this.snippet.expectCurlRequest("multipart-post").withContents( + this.snippet.expectCurlRequest("multipart-post-with-parameters").withContents( codeBlock("bash").content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("image", - "documents/images/example.png", null, "bytes".getBytes()); - new CurlRequestSnippet().document( - "multipart-post", - result(fileUpload("/upload").file(multipartFile) - .param("a", "apple", "avocado").param("b", "banana"))); + new CurlRequestSnippet().document(new OperationBuilder( + "multipart-post-with-parameters").request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .submittedFileName("documents/images/example.png").and() + .param("a", "apple", "avocado").param("b", "banana").build()); } @Test @@ -337,8 +244,11 @@ public class CurlRequestSnippetTests { "src/test/resources/custom-snippet-templates/curl-request-with-title.snippet")); request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); - new CurlRequestSnippet(attributes(key("title").value( - "curl request title"))).document("custom-attributes", result(request)); + new CurlRequestSnippet(attributes(key("title").value("curl request title"))) + .document(new OperationBuilder("custom-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)) + .request("http://localhost/foo").build()); } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java index 4d569339..3eacda6e 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java @@ -19,14 +19,9 @@ package org.springframework.restdocs.http; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.fileUpload; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.httpRequest; -import static org.springframework.restdocs.test.StubMvcResult.result; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.PUT; @@ -38,12 +33,11 @@ import org.junit.Test; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockMultipartFile; 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; /** * Tests for {@link HttpRequestSnippet} @@ -65,8 +59,8 @@ public class HttpRequestSnippetTests { httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header( "Alpha", "a")); - new HttpRequestSnippet().document("get-request", result(get("/foo") - .header("Alpha", "a"))); + new HttpRequestSnippet().document(new OperationBuilder("get-request") + .request("http://localhost/foo").header("Alpha", "a").build()); } @Test @@ -74,17 +68,9 @@ public class HttpRequestSnippetTests { this.snippet.expectHttpRequest("get-request-with-query-string").withContents( httpRequest(GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost")); - new HttpRequestSnippet().document("get-request-with-query-string", - result(get("/foo?bar=baz"))); - } - - @Test - public void getRequestWithParameter() throws IOException { - this.snippet.expectHttpRequest("get-request-with-parameter").withContents( - httpRequest(GET, "/foo?b%26r=baz").header(HttpHeaders.HOST, "localhost")); - - new HttpRequestSnippet().document("get-request-with-parameter", - result(get("/foo").param("b&r", "baz"))); + new HttpRequestSnippet().document(new OperationBuilder( + "get-request-with-query-string").request("http://localhost/foo?bar=baz") + .build()); } @Test @@ -93,8 +79,9 @@ public class HttpRequestSnippetTests { httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content( "Hello, world")); - new HttpRequestSnippet().document("post-request-with-content", - result(post("/foo").content("Hello, world"))); + new HttpRequestSnippet().document(new OperationBuilder( + "post-request-with-content").request("http://localhost/foo") + .method("POST").content("Hello, world").build()); } @Test @@ -104,8 +91,9 @@ public class HttpRequestSnippetTests { .header("Content-Type", "application/x-www-form-urlencoded") .content("b%26r=baz&a=alpha")); - new HttpRequestSnippet().document("post-request-with-parameter", - result(post("/foo").param("b&r", "baz").param("a", "alpha"))); + new HttpRequestSnippet().document(new OperationBuilder( + "post-request-with-parameter").request("http://localhost/foo") + .method("POST").param("b&r", "baz").param("a", "alpha").build()); } @Test @@ -114,8 +102,10 @@ public class HttpRequestSnippetTests { httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content( "Hello, world")); - new HttpRequestSnippet().document("put-request-with-content", - result(put("/foo").content("Hello, world"))); + new HttpRequestSnippet() + .document(new OperationBuilder("put-request-with-content") + .request("http://localhost/foo").method("PUT") + .content("Hello, world").build()); } @Test @@ -125,8 +115,9 @@ public class HttpRequestSnippetTests { .header("Content-Type", "application/x-www-form-urlencoded") .content("b%26r=baz&a=alpha")); - new HttpRequestSnippet().document("put-request-with-parameter", - result(put("/foo").param("b&r", "baz").param("a", "alpha"))); + new HttpRequestSnippet().document(new OperationBuilder( + "put-request-with-parameter").request("http://localhost/foo") + .method("PUT").param("b&r", "baz").param("a", "alpha").build()); } @Test @@ -139,10 +130,10 @@ public class HttpRequestSnippetTests { .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("image", - "documents/images/example.png", null, "<< data >>".getBytes()); - new HttpRequestSnippet().document("multipart-post", - result(fileUpload("/upload").file(multipartFile))); + new HttpRequestSnippet().document(new OperationBuilder("multipart-post") + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", "<< data >>".getBytes()).build()); } @Test @@ -156,18 +147,18 @@ public class HttpRequestSnippetTests { String filePart = createPart(String.format("Content-Disposition: form-data; " + "name=image%n%n<< data >>")); String expectedContent = param1Part + param2Part + param3Part + filePart; - this.snippet.expectHttpRequest("multipart-post").withContents( + this.snippet.expectHttpRequest("multipart-post-with-parameters").withContents( httpRequest(POST, "/upload") .header(HttpHeaders.HOST, "localhost") .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("image", - "documents/images/example.png", null, "<< data >>".getBytes()); - new HttpRequestSnippet().document( - "multipart-post", - result(fileUpload("/upload").file(multipartFile) - .param("a", "apple", "avocado").param("b", "banana"))); + new HttpRequestSnippet().document(new OperationBuilder( + "multipart-post-with-parameters").request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .param("a", "apple", "avocado").param("b", "banana") + .part("image", "<< data >>".getBytes()).build()); } @Test @@ -181,50 +172,37 @@ public class HttpRequestSnippetTests { .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) .content(expectedContent)); - MockMultipartFile multipartFile = new MockMultipartFile("image", - "documents/images/example.png", MediaType.IMAGE_PNG_VALUE, - "<< data >>".getBytes()); - new HttpRequestSnippet().document( - "multipart-post-with-content-type", - result(fileUpload("/upload").file(multipartFile))); - } - - @Test - public void getRequestWithCustomServerName() throws IOException { - this.snippet.expectHttpRequest("get-request-custom-server-name").withContents( - httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com")); - - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - request.setServerName("api.example.com"); - - new HttpRequestSnippet().document("get-request-custom-server-name", - result(request)); + new HttpRequestSnippet().document(new OperationBuilder( + "multipart-post-with-content-type").request("http://localhost/upload") + .method("POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", "<< data >>".getBytes()) + .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE).build()); } @Test public void getRequestWithCustomHost() throws IOException { this.snippet.expectHttpRequest("get-request-custom-host").withContents( httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com")); - - new HttpRequestSnippet().document("get-request-custom-host", - result(get("/foo").header(HttpHeaders.HOST, "api.example.com"))); + new HttpRequestSnippet().document(new OperationBuilder("get-request-custom-host") + .request("http://localhost/foo") + .header(HttpHeaders.HOST, "api.example.com").build()); } @Test public void requestWithCustomSnippetAttributes() throws IOException { this.snippet.expectHttpRequest("request-with-snippet-attributes").withContents( containsString("Title for the request")); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("http-request")) .thenReturn( new FileSystemResource( "src/test/resources/custom-snippet-templates/http-request-with-title.snippet")); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - new HttpRequestSnippet(attributes(key("title").value( - "Title for the request"))).document("request-with-snippet-attributes", - result(request)); + new HttpRequestSnippet(attributes(key("title").value("Title for the request"))) + .document(new OperationBuilder("request-with-snippet-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)) + .request("http://localhost/foo").build()); } private String createPart(String content) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java index f9f7bac5..068d6b88 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java @@ -24,20 +24,19 @@ import static org.springframework.http.HttpStatus.OK; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.httpResponse; -import static org.springframework.restdocs.test.StubMvcResult.result; import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; +import org.springframework.restdocs.test.OperationBuilder; /** * Tests for {@link HttpResponseSnippet} @@ -53,18 +52,16 @@ public class HttpResponseSnippetTests { @Test public void basicResponse() throws IOException { this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK)); - new HttpResponseSnippet().document("basic-response", result()); + new HttpResponseSnippet() + .document(new OperationBuilder("basic-response").build()); } @Test public void nonOkResponse() throws IOException { this.snippet.expectHttpResponse("non-ok-response").withContents( httpResponse(BAD_REQUEST)); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.setStatus(BAD_REQUEST.value()); - new HttpResponseSnippet().document("non-ok-response", - result(response)); + new HttpResponseSnippet().document(new OperationBuilder("non-ok-response") + .response().status(BAD_REQUEST.value()).build()); } @Test @@ -73,39 +70,33 @@ public class HttpResponseSnippetTests { httpResponse(OK) // .header("Content-Type", "application/json") // .header("a", "alpha")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setHeader("a", "alpha"); - new HttpResponseSnippet().document("response-with-headers", - result(response)); + new HttpResponseSnippet().document(new OperationBuilder("response-with-headers") + .response() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header("a", "alpha").build()); } @Test public void responseWithContent() throws IOException { this.snippet.expectHttpResponse("response-with-content").withContents( httpResponse(OK).content("content")); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter().append("content"); - new HttpResponseSnippet().document("response-with-content", - result(response)); + new HttpResponseSnippet().document(new OperationBuilder("response-with-content") + .response().content("content").build()); } @Test public void responseWithCustomSnippetAttributes() throws IOException { this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents( containsString("Title for the response")); - MockHttpServletRequest request = new MockHttpServletRequest(); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("http-response")) .thenReturn( new FileSystemResource( "src/test/resources/custom-snippet-templates/http-response-with-title.snippet")); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - new HttpResponseSnippet(attributes(key("title").value( - "Title for the response"))).document("response-with-snippet-attributes", - result(request)); + new HttpResponseSnippet(attributes(key("title").value("Title for the response"))) + .document(new OperationBuilder("response-with-snippet-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).build()); } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java index 1c258a26..161fefbf 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java @@ -26,8 +26,10 @@ import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.operation.StandardOperationResponse; /** * Tests for {@link ContentTypeLinkExtractor}. @@ -39,12 +41,11 @@ public class ContentTypeLinkExtractorTests { @Rule public ExpectedException thrown = ExpectedException.none(); - private final MockHttpServletResponse response = new MockHttpServletResponse(); - @Test public void extractionFailsWithNullContentType() throws IOException { this.thrown.expect(IllegalStateException.class); - new ContentTypeLinkExtractor().extractLinks(this.response); + new ContentTypeLinkExtractor().extractLinks(new StandardOperationResponse( + HttpStatus.OK, new HttpHeaders(), null)); } @Test @@ -52,9 +53,12 @@ public class ContentTypeLinkExtractorTests { Map extractors = new HashMap<>(); LinkExtractor extractor = mock(LinkExtractor.class); extractors.put(MediaType.APPLICATION_JSON, extractor); - this.response.setContentType("application/json"); - new ContentTypeLinkExtractor(extractors).extractLinks(this.response); - verify(extractor).extractLinks(this.response); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK, + httpHeaders, null); + new ContentTypeLinkExtractor(extractors).extractLinks(response); + verify(extractor).extractLinks(response); } @Test @@ -62,9 +66,12 @@ public class ContentTypeLinkExtractorTests { Map extractors = new HashMap<>(); LinkExtractor extractor = mock(LinkExtractor.class); extractors.put(MediaType.APPLICATION_JSON, extractor); - this.response.setContentType("application/json;foo=bar"); - new ContentTypeLinkExtractor(extractors).extractLinks(this.response); - verify(extractor).extractLinks(this.response); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.parseMediaType("application/json;foo=bar")); + StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK, + httpHeaders, null); + new ContentTypeLinkExtractor(extractors).extractLinks(response); + verify(extractor).extractLinks(response); } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java index 9e6256d7..6711ae19 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java @@ -19,7 +19,6 @@ package org.springframework.restdocs.hypermedia; import static org.junit.Assert.assertEquals; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -31,7 +30,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.StandardOperationResponse; import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -105,11 +106,9 @@ public class LinkExtractorsPayloadTests { assertEquals(expectedLinksByRel, actualLinks); } - private MockHttpServletResponse createResponse(String contentName) throws IOException { - MockHttpServletResponse response = new MockHttpServletResponse(); - FileCopyUtils.copy(new FileReader(getPayloadFile(contentName)), - response.getWriter()); - return response; + private OperationResponse createResponse(String contentName) throws IOException { + return new StandardOperationResponse(HttpStatus.OK, null, + FileCopyUtils.copyToByteArray(getPayloadFile(contentName))); } private File getPayloadFile(String name) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java index 99ad2bc6..97979bdc 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksSnippetTests.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.when; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; import java.io.IOException; import java.util.Arrays; @@ -34,12 +33,13 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.operation.OperationResponse; 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 org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -61,9 +61,9 @@ public class LinksSnippetTests { this.thrown.expect(SnippetException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [foo]")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", - "bar")), Collections. emptyList()).document( - "undocumented-link", result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "bar")), + Collections. emptyList()).document(new OperationBuilder( + "undocumented-link").build()); } @Test @@ -71,9 +71,9 @@ public class LinksSnippetTests { this.thrown.expect(SnippetException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " found in the response: [foo]")); - new LinksSnippet(new StubLinkExtractor(), - Arrays.asList(new LinkDescriptor("foo").description("bar"))).document( - "missing-link", result()); + new LinksSnippet(new StubLinkExtractor(), Arrays.asList(new LinkDescriptor("foo") + .description("bar"))).document(new OperationBuilder("missing-link") + .build()); } @Test @@ -81,9 +81,9 @@ public class LinksSnippetTests { this.snippet.expectLinks("documented-optional-link").withContents( // tableWithHeader("Relation", "Description") // .row("foo", "bar")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", - "blah")), Arrays.asList(new LinkDescriptor("foo").description("bar") - .optional())).document("documented-optional-link", result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", "blah")), + Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) + .document(new OperationBuilder("documented-optional-link").build()); } @Test @@ -91,9 +91,9 @@ public class LinksSnippetTests { this.snippet.expectLinks("missing-optional-link").withContents( // tableWithHeader("Relation", "Description") // .row("foo", "bar")); - new LinksSnippet(new StubLinkExtractor(), - Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) - .document("missing-optional-link", result()); + new LinksSnippet(new StubLinkExtractor(), Arrays.asList(new LinkDescriptor("foo") + .description("bar").optional())).document(new OperationBuilder( + "missing-optional-link").build()); } @Test @@ -102,9 +102,10 @@ public class LinksSnippetTests { this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [a]. Links with the following relations were not" + " found in the response: [foo]")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", - "alpha")), Arrays.asList(new LinkDescriptor("foo").description("bar"))) - .document("undocumented-link-and-missing-link", result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha")), + Arrays.asList(new LinkDescriptor("foo").description("bar"))) + .document(new OperationBuilder("undocumented-link-and-missing-link") + .build()); } @Test @@ -113,11 +114,11 @@ public class LinksSnippetTests { tableWithHeader("Relation", "Description") // .row("a", "one") // .row("b", "two")); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", - "alpha"), new Link("b", "bravo")), Arrays.asList( + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), + new Link("b", "bravo")), Arrays.asList( new LinkDescriptor("a").description("one"), - new LinkDescriptor("b").description("two"))).document("documented-links", - result()); + new LinkDescriptor("b").description("two"))) + .document(new OperationBuilder("documented-links").build()); } @Test @@ -134,33 +135,34 @@ public class LinksSnippetTests { "src/test/resources/custom-snippet-templates/links-with-extra-column.snippet")); request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", - "alpha"), new Link("b", "bravo")), Arrays.asList( + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), + new Link("b", "bravo")), Arrays.asList( new LinkDescriptor("a").description("one").attributes( key("foo").value("alpha")), new LinkDescriptor("b").description("two").attributes( - key("foo").value("bravo")))).document( - "links-with-custom-descriptor-attributes", result(request)); + key("foo").value("bravo")))).document(new OperationBuilder( + "links-with-custom-descriptor-attributes").attribute( + TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) + .build()); } @Test public void linksWithCustomAttributes() throws IOException { this.snippet.expectLinks("links-with-custom-attributes").withContents( startsWith(".Title for the links")); - MockHttpServletRequest request = new MockHttpServletRequest(); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("links")) .thenReturn( new FileSystemResource( "src/test/resources/custom-snippet-templates/links-with-title.snippet")); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", - "alpha"), new Link("b", "bravo")), attributes(key("title").value( + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", "alpha"), + new Link("b", "bravo")), attributes(key("title").value( "Title for the links")), Arrays.asList( new LinkDescriptor("a").description("one"), - new LinkDescriptor("b").description("two"))).document( - "links-with-custom-attributes", result(request)); + new LinkDescriptor("b").description("two"))) + .document(new OperationBuilder("links-with-custom-attributes").attribute( + TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).build()); } private static class StubLinkExtractor implements LinkExtractor { @@ -168,7 +170,7 @@ public class LinksSnippetTests { private MultiValueMap linksByRel = new LinkedMultiValueMap(); @Override - public MultiValueMap extractLinks(MockHttpServletResponse response) + public MultiValueMap extractLinks(OperationResponse response) throws IOException { return this.linksByRel; } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/MockMvcOperationRequestFactoryTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/MockMvcOperationRequestFactoryTests.java new file mode 100644 index 00000000..9d286610 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/MockMvcOperationRequestFactoryTests.java @@ -0,0 +1,220 @@ +package org.springframework.restdocs.hypermedia; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasEntry; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.util.Arrays; + +import javax.servlet.http.Part; + +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockServletContext; +import org.springframework.restdocs.operation.MockMvcOperationRequestFactory; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +/** + * Tests for {@link MockMvcOperationRequestFactory} + * + * @author Andy Wilkinson + */ +public class MockMvcOperationRequestFactoryTests { + + private final MockMvcOperationRequestFactory factory = new MockMvcOperationRequestFactory(); + + @Test + public void httpRequest() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .get("/foo")); + assertThat(request.getUri(), is(URI.create("http://localhost/foo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void httpRequestWithCustomPort() throws Exception { + MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo") + .buildRequest(new MockServletContext()); + mockRequest.setServerPort(8080); + OperationRequest request = this.factory.createOperationRequest(mockRequest); + assertThat(request.getUri(), is(URI.create("http://localhost:8080/foo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void requestWithContextPath() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get( + "/foo/bar").contextPath("/foo")); + assertThat(request.getUri(), is(URI.create("http://localhost/foo/bar"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void requestWithHeaders() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .get("/foo").header("a", "alpha", "apple").header("b", "bravo")); + assertThat(request.getUri(), is(URI.create("http://localhost/foo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + assertThat(request.getHeaders(), hasEntry("a", Arrays.asList("alpha", "apple"))); + assertThat(request.getHeaders(), hasEntry("b", Arrays.asList("bravo"))); + } + + @Test + public void httpsRequest() throws Exception { + MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo") + .buildRequest(new MockServletContext()); + mockRequest.setScheme("https"); + mockRequest.setServerPort(443); + OperationRequest request = this.factory.createOperationRequest(mockRequest); + assertThat(request.getUri(), is(URI.create("https://localhost/foo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void httpsRequestWithCustomPort() throws Exception { + MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo") + .buildRequest(new MockServletContext()); + mockRequest.setScheme("https"); + mockRequest.setServerPort(8443); + OperationRequest request = this.factory.createOperationRequest(mockRequest); + assertThat(request.getUri(), is(URI.create("https://localhost:8443/foo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void getRequestWithParametersProducesUriWithQueryString() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .get("/foo").param("a", "alpha", "apple").param("b", "br&vo")); + assertThat(request.getUri(), + is(URI.create("http://localhost/foo?a=alpha&a=apple&b=br%26vo"))); + assertThat(request.getParameters().size(), is(2)); + assertThat(request.getParameters(), + hasEntry("a", Arrays.asList("alpha", "apple"))); + assertThat(request.getParameters(), hasEntry("b", Arrays.asList("br&vo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void getRequestWithQueryStringPopulatesParameters() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .get("/foo?a=alpha&b=bravo")); + assertThat(request.getUri(), + is(URI.create("http://localhost/foo?a=alpha&b=bravo"))); + assertThat(request.getParameters().size(), is(2)); + assertThat(request.getParameters(), hasEntry("a", Arrays.asList("alpha"))); + assertThat(request.getParameters(), hasEntry("b", Arrays.asList("bravo"))); + assertThat(request.getMethod(), is(HttpMethod.GET)); + } + + @Test + public void postRequestWithParameters() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .post("/foo").param("a", "alpha", "apple").param("b", "br&vo")); + assertThat(request.getUri(), is(URI.create("http://localhost/foo"))); + assertThat(request.getMethod(), is(HttpMethod.POST)); + assertThat(request.getParameters().size(), is(2)); + assertThat(request.getParameters(), + hasEntry("a", Arrays.asList("alpha", "apple"))); + assertThat(request.getParameters(), hasEntry("b", Arrays.asList("br&vo"))); + } + + @Test + public void mockMultipartFileUpload() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .fileUpload("/foo").file( + new MockMultipartFile("file", new byte[] { 1, 2, 3, 4 }))); + assertThat(request.getUri(), is(URI.create("http://localhost/foo"))); + assertThat(request.getMethod(), is(HttpMethod.POST)); + assertThat(request.getParts().size(), is(1)); + OperationRequestPart part = request.getParts().iterator().next(); + assertThat(part.getName(), is(equalTo("file"))); + assertThat(part.getSubmittedFileName(), is(nullValue())); + assertThat(part.getHeaders().isEmpty(), is(true)); + assertThat(part.getContent(), is(equalTo(new byte[] { 1, 2, 3, 4 }))); + } + + @Test + public void mockMultipartFileUploadWithContentType() throws Exception { + OperationRequest request = createOperationRequest(MockMvcRequestBuilders + .fileUpload("/foo").file( + new MockMultipartFile("file", "original", "image/png", + new byte[] { 1, 2, 3, 4 }))); + assertThat(request.getUri(), is(URI.create("http://localhost/foo"))); + assertThat(request.getMethod(), is(HttpMethod.POST)); + assertThat(request.getParts().size(), is(1)); + OperationRequestPart part = request.getParts().iterator().next(); + assertThat(part.getName(), is(equalTo("file"))); + assertThat(part.getSubmittedFileName(), is(equalTo("original"))); + assertThat(part.getHeaders().getContentType(), is(MediaType.IMAGE_PNG)); + assertThat(part.getContent(), is(equalTo(new byte[] { 1, 2, 3, 4 }))); + } + + @Test + public void requestWithPart() throws Exception { + MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo") + .buildRequest(new MockServletContext()); + Part mockPart = mock(Part.class); + when(mockPart.getHeaderNames()).thenReturn(Arrays.asList("a", "b")); + when(mockPart.getHeaders("a")).thenReturn(Arrays.asList("alpha")); + when(mockPart.getHeaders("b")).thenReturn(Arrays.asList("bravo", "banana")); + when(mockPart.getInputStream()).thenReturn( + new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 })); + when(mockPart.getName()).thenReturn("part-name"); + when(mockPart.getSubmittedFileName()).thenReturn("submitted.txt"); + mockRequest.addPart(mockPart); + OperationRequest request = this.factory.createOperationRequest(mockRequest); + assertThat(request.getParts().size(), is(1)); + OperationRequestPart part = request.getParts().iterator().next(); + assertThat(part.getName(), is(equalTo("part-name"))); + assertThat(part.getSubmittedFileName(), is(equalTo("submitted.txt"))); + assertThat(part.getHeaders().getContentType(), is(nullValue())); + assertThat(part.getHeaders().get("a"), contains("alpha")); + assertThat(part.getHeaders().get("b"), contains("bravo", "banana")); + assertThat(part.getContent(), is(equalTo(new byte[] { 1, 2, 3, 4 }))); + } + + @Test + public void requestWithPartWithContentType() throws Exception { + MockHttpServletRequest mockRequest = MockMvcRequestBuilders.get("/foo") + .buildRequest(new MockServletContext()); + Part mockPart = mock(Part.class); + when(mockPart.getHeaderNames()).thenReturn(Arrays.asList("a", "b")); + when(mockPart.getHeaders("a")).thenReturn(Arrays.asList("alpha")); + when(mockPart.getHeaders("b")).thenReturn(Arrays.asList("bravo", "banana")); + when(mockPart.getInputStream()).thenReturn( + new ByteArrayInputStream(new byte[] { 1, 2, 3, 4 })); + when(mockPart.getName()).thenReturn("part-name"); + when(mockPart.getSubmittedFileName()).thenReturn("submitted.png"); + when(mockPart.getContentType()).thenReturn("image/png"); + mockRequest.addPart(mockPart); + OperationRequest request = this.factory.createOperationRequest(mockRequest); + assertThat(request.getParts().size(), is(1)); + OperationRequestPart part = request.getParts().iterator().next(); + assertThat(part.getName(), is(equalTo("part-name"))); + assertThat(part.getSubmittedFileName(), is(equalTo("submitted.png"))); + assertThat(part.getHeaders().getContentType(), is(MediaType.IMAGE_PNG)); + assertThat(part.getHeaders().get("a"), contains("alpha")); + assertThat(part.getHeaders().get("b"), contains("bravo", "banana")); + assertThat(part.getContent(), is(equalTo(new byte[] { 1, 2, 3, 4 }))); + } + + private OperationRequest createOperationRequest(MockHttpServletRequestBuilder builder) + throws Exception { + return this.factory.createOperationRequest(builder + .buildRequest(new MockServletContext())); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java index fb257e54..bec7ee7e 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java @@ -21,12 +21,10 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; import java.io.IOException; import java.util.Arrays; @@ -36,14 +34,14 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.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; /** * Tests for {@link RequestFieldsSnippet} @@ -68,9 +66,9 @@ public class RequestFieldsSnippetTests { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"), - fieldWithPath("a").description("three"))).document( - "map-request-with-fields", - result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"))); + fieldWithPath("a").description("three"))).document(new OperationBuilder( + "map-request-with-fields").request("http://localhost") + .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); } @Test @@ -83,10 +81,9 @@ public class RequestFieldsSnippetTests { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a") - .description("three"))).document( - "array-request-with-fields", - result(get("/foo").content( - "[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"))); + .description("three"))).document(new OperationBuilder( + "array-request-with-fields").request("http://localhost") + .content("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]").build()); } @Test @@ -95,8 +92,9 @@ public class RequestFieldsSnippetTests { this.thrown .expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - new RequestFieldsSnippet(Collections. emptyList()).document( - "undocumented-request-field", result(get("/foo").content("{\"a\": 5}"))); + new RequestFieldsSnippet(Collections. emptyList()) + .document(new OperationBuilder("undocumented-request-field") + .request("http://localhost").content("{\"a\": 5}").build()); } @Test @@ -106,15 +104,17 @@ public class RequestFieldsSnippetTests { .expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a.b]")); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) - .document("missing-request-fields", result(get("/foo").content("{}"))); + .document(new OperationBuilder("missing-request-fields") + .request("http://localhost").content("{}").build()); } @Test public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException { this.thrown.expect(FieldTypeRequiredException.class); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one") - .optional())).document("missing-optional-request-field-with-no-type", - result(get("/foo").content("{ }"))); + .optional())).document(new OperationBuilder( + "missing-optional-request-field-with-no-type") + .request("http://localhost").content("{ }").build()); } @Test @@ -127,8 +127,10 @@ public class RequestFieldsSnippetTests { .expectMessage(endsWith("Fields with the following paths were not found" + " in the payload: [a.b]")); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"))) - .document("undocumented-request-field-and-missing-request-field", - result(get("/foo").content("{ \"a\": { \"c\": 5 }}"))); + .document(new OperationBuilder( + "undocumented-request-field-and-missing-request-field") + .request("http://localhost").content("{ \"a\": { \"c\": 5 }}") + .build()); } @Test @@ -142,18 +144,17 @@ public class RequestFieldsSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("request-fields")).thenReturn( snippetResource("request-fields-with-extra-column")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()); new RequestFieldsSnippet(Arrays.asList( fieldWithPath("a.b").description("one").attributes( key("foo").value("alpha")), fieldWithPath("a.c").description("two").attributes( key("foo").value("bravo")), fieldWithPath("a").description("three").attributes( - key("foo").value("charlie")))).document( - "request-fields-with-custom-descriptor-attributes", result(request)); + key("foo").value("charlie")))).document(new OperationBuilder( + "request-fields-with-custom-descriptor-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).request("http://localhost") + .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); } @Test @@ -163,14 +164,12 @@ public class RequestFieldsSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("request-fields")).thenReturn( snippetResource("request-fields-with-title")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.setContent("{\"a\": \"foo\"}".getBytes()); - MockHttpServletResponse response = new MockHttpServletResponse(); new RequestFieldsSnippet(attributes(key("title").value("Custom title")), - Arrays.asList(fieldWithPath("a").description("one"))).document( - "request-fields-with-custom-attributes", result(request, response)); + Arrays.asList(fieldWithPath("a").description("one"))) + .document(new OperationBuilder("request-fields-with-custom-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)) + .request("http://localhost").content("{\"a\": \"foo\"}").build()); } @Test @@ -183,10 +182,12 @@ public class RequestFieldsSnippetTests { new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one") .type("b"), fieldWithPath("a/c").description("two").type("c"), - fieldWithPath("a").description("three").type("a"))).document( - "xml-request", - result(get("/foo").content("5charlie").contentType( - MediaType.APPLICATION_XML))); + fieldWithPath("a").description("three").type("a"))) + .document(new OperationBuilder("xml-request") + .request("http://localhost") + .content("5charlie") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -195,19 +196,23 @@ public class RequestFieldsSnippetTests { this.thrown .expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - new RequestFieldsSnippet(Collections. emptyList()).document( - "undocumented-xml-request-field", - result(get("/foo").content("5").contentType( - MediaType.APPLICATION_XML))); + new RequestFieldsSnippet(Collections. emptyList()) + .document(new OperationBuilder("undocumented-xml-request-field") + .request("http://localhost") + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test public void xmlRequestFieldWithNoType() throws IOException { this.thrown.expect(FieldTypeRequiredException.class); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document("missing-xml-request", - result(get("/foo").contentType(MediaType.APPLICATION_XML) - .content("5"))); + .document(new OperationBuilder("missing-xml-request") + .request("http://localhost") + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -217,10 +222,11 @@ public class RequestFieldsSnippetTests { .expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a/b]")); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"), - fieldWithPath("a").description("one"))).document( - "missing-xml-request-fields", - result(get("/foo").contentType(MediaType.APPLICATION_XML).content( - ""))); + fieldWithPath("a").description("one"))).document(new OperationBuilder( + "missing-xml-request-fields").request("http://localhost") + .content("") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -233,9 +239,12 @@ public class RequestFieldsSnippetTests { .expectMessage(endsWith("Fields with the following paths were not found" + " in the payload: [a/b]")); new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"))) - .document("undocumented-xml-request-field-and-missing-xml-request-field", - result(get("/foo").contentType(MediaType.APPLICATION_XML) - .content("5"))); + .document(new OperationBuilder( + "undocumented-xml-request-field-and-missing-xml-request-field") + .request("http://localhost") + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } private FileSystemResource snippetResource(String name) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java index a768f05b..9018f96d 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java @@ -24,7 +24,6 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; import java.io.IOException; import java.util.Arrays; @@ -34,14 +33,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.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; /** * Tests for {@link PayloadDocumentation} @@ -66,18 +66,18 @@ public class ResponseFieldsSnippetTests { .row("assets[]", "Object", "four") // .row("assets[].id", "Number", "five") // .row("assets[].name", "String", "six")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter().append( - "{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" - + " [{\"id\":356,\"name\": \"sample\"}]}"); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"), fieldWithPath("date").description("two"), fieldWithPath("assets") .description("three"), fieldWithPath("assets[]").description("four"), fieldWithPath("assets[].id").description("five"), - fieldWithPath("assets[].name").description("six"))).document( - "map-response-with-fields", result(response)); + fieldWithPath("assets[].name").description("six"))) + .document(new OperationBuilder("map-response-with-fields") + .response() + .content( + "{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + + " [{\"id\":356,\"name\": \"sample\"}]}") + .build()); } @Test @@ -87,14 +87,12 @@ public class ResponseFieldsSnippetTests { .row("[]a.b", "Number", "one") // .row("[]a.c", "String", "two") // .row("[]a", "Object", "three")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter() - .append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"); new ResponseFieldsSnippet(Arrays.asList( fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c") .description("two"), fieldWithPath("[]a").description("three"))) - .document("array-response-with-fields", result(response)); + .document(new OperationBuilder("array-response-with-fields").response() + .content("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]") + .build()); } @Test @@ -102,11 +100,9 @@ public class ResponseFieldsSnippetTests { this.snippet.expectResponseFields("array-response").withContents( // tableWithHeader("Path", "Type", "Description") // .row("[]", "String", "one")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter().append("[\"a\", \"b\", \"c\"]"); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one"))) - .document("array-response", result(response)); + .document(new OperationBuilder("array-response").response() + .content("[\"a\", \"b\", \"c\"]").build()); } @Test @@ -120,19 +116,17 @@ public class ResponseFieldsSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("response-fields")).thenReturn( snippetResource("response-fields-with-extra-column")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getOutputStream().print("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"); new ResponseFieldsSnippet(Arrays.asList( fieldWithPath("a.b").description("one").attributes( key("foo").value("alpha")), fieldWithPath("a.c").description("two").attributes( key("foo").value("bravo")), fieldWithPath("a").description("three").attributes( - key("foo").value("charlie")))).document( - "response-fields-with-custom-attributes", result(request, response)); + key("foo").value("charlie")))).document(new OperationBuilder( + "response-fields-with-custom-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).response() + .content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build()); } @Test @@ -142,14 +136,12 @@ public class ResponseFieldsSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("response-fields")).thenReturn( snippetResource("response-fields-with-title")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getOutputStream().print("{\"a\": \"foo\"}"); new ResponseFieldsSnippet(attributes(key("title").value("Custom title")), - Arrays.asList(fieldWithPath("a").description("one"))).document( - "response-fields-with-custom-attributes", result(request, response)); + Arrays.asList(fieldWithPath("a").description("one"))) + .document(new OperationBuilder("response-fields-with-custom-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).response() + .content("{\"a\": \"foo\"}").build()); } @Test @@ -159,13 +151,14 @@ public class ResponseFieldsSnippetTests { .row("a/b", "b", "one") // .row("a/c", "c", "two") // .row("a", "a", "three")); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.setContentType(MediaType.APPLICATION_XML_VALUE); - response.getOutputStream().print("5charlie"); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one") .type("b"), fieldWithPath("a/c").description("two").type("c"), - fieldWithPath("a").description("three").type("a"))).document( - "xml-response", result(response)); + fieldWithPath("a").description("three").type("a"))) + .document(new OperationBuilder("xml-response") + .response() + .content("5charlie") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -177,8 +170,12 @@ public class ResponseFieldsSnippetTests { MockHttpServletResponse response = new MockHttpServletResponse(); response.setContentType(MediaType.APPLICATION_XML_VALUE); response.getOutputStream().print("5"); - new ResponseFieldsSnippet(Collections. emptyList()).document( - "undocumented-xml-response-field", result(response)); + new ResponseFieldsSnippet(Collections. emptyList()) + .document(new OperationBuilder("undocumented-xml-response-field") + .response() + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -188,7 +185,11 @@ public class ResponseFieldsSnippetTests { response.setContentType(MediaType.APPLICATION_XML_VALUE); response.getOutputStream().print("5"); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) - .document("xml-response-no-field-type", result(response)); + .document(new OperationBuilder("xml-response-no-field-type") + .response() + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -201,8 +202,10 @@ public class ResponseFieldsSnippetTests { response.setContentType(MediaType.APPLICATION_XML_VALUE); response.getOutputStream().print(""); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"), - fieldWithPath("a").description("one"))).document( - "missing-xml-response-field", result(response)); + fieldWithPath("a").description("one"))).document(new OperationBuilder( + "missing-xml-response-field").response().content("") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } @Test @@ -219,8 +222,12 @@ public class ResponseFieldsSnippetTests { response.setContentType(MediaType.APPLICATION_XML_VALUE); response.getOutputStream().print("5"); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"))) - .document("undocumented-xml-request-field-and-missing-xml-request-field", - result(response)); + .document(new OperationBuilder( + "undocumented-xml-request-field-and-missing-xml-request-field") + .response() + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); } private FileSystemResource snippetResource(String name) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java index 43ed107b..c4204d8c 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersSnippetTests.java @@ -25,8 +25,6 @@ import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; import static org.springframework.restdocs.test.SnippetMatchers.tableWithTitleAndHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; -import static org.springframework.restdocs.test.TestRequestBuilders.get; import java.io.IOException; import java.util.Arrays; @@ -36,12 +34,12 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; -import org.springframework.restdocs.config.RestDocumentationContext; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; +import org.springframework.restdocs.test.OperationBuilder; /** * Tests for {@link PathParametersSnippet} @@ -63,7 +61,8 @@ public class PathParametersSnippetTests { this.thrown.expectMessage(equalTo("Path parameters with the following names were" + " not documented: [a]")); new PathParametersSnippet(Collections. emptyList()) - .document("undocumented-path-parameter", result(get("/{a}/", "alpha"))); + .document(new OperationBuilder("undocumented-path-parameter").attribute( + "org.springframework.restdocs.urlTemplate", "/{a}/").build()); } @Test @@ -72,8 +71,9 @@ public class PathParametersSnippetTests { this.thrown.expectMessage(equalTo("Path parameters with the following names were" + " not found in the request: [a]")); new PathParametersSnippet( - Arrays.asList(parameterWithName("a").description("one"))).document( - "missing-path-parameter", result(get("/"))); + Arrays.asList(parameterWithName("a").description("one"))) + .document(new OperationBuilder("missing-path-parameter").attribute( + "org.springframework.restdocs.urlTemplate", "/").build()); } @Test @@ -83,8 +83,10 @@ public class PathParametersSnippetTests { + " not documented: [b]. Path parameters with the following" + " names were not found in the request: [a]")); new PathParametersSnippet( - Arrays.asList(parameterWithName("a").description("one"))).document( - "undocumented-and-missing-path-parameters", result(get("/{b}", "bravo"))); + Arrays.asList(parameterWithName("a").description("one"))) + .document(new OperationBuilder("undocumented-and-missing-path-parameters") + .attribute("org.springframework.restdocs.urlTemplate", "/{b}") + .build()); } @Test @@ -94,11 +96,9 @@ public class PathParametersSnippetTests { "one").row("b", "two")); new PathParametersSnippet(Arrays.asList( parameterWithName("a").description("one"), parameterWithName("b") - .description("two"))).document( - "path-parameters", - result(get("/{a}/{b}", "alpha", "banana").requestAttr( - RestDocumentationContext.class.getName(), - new RestDocumentationContext(null)))); + .description("two"))).document(new OperationBuilder( + "path-parameters").attribute("org.springframework.restdocs.urlTemplate", + "/{a}/{b}").build()); } @Test @@ -109,11 +109,9 @@ public class PathParametersSnippetTests { .row("a", "one").row("b", "two")); new PathParametersSnippet(Arrays.asList( parameterWithName("a").description("one"), parameterWithName("b") - .description("two"))).document( - "path-parameters-with-query-string", - result(get("/{a}/{b}?foo=bar", "alpha", "banana").requestAttr( - RestDocumentationContext.class.getName(), - new RestDocumentationContext(null)))); + .description("two"))).document(new OperationBuilder( + "path-parameters-with-query-string").attribute( + "org.springframework.restdocs.urlTemplate", "/{a}/{b}?foo=bar").build()); } @Test @@ -127,11 +125,12 @@ public class PathParametersSnippetTests { snippetResource("path-parameters-with-extra-column")); new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one") .attributes(key("foo").value("alpha")), parameterWithName("b") - .description("two").attributes(key("foo").value("bravo")))).document( - "path-parameters-with-custom-descriptor-attributes", - result(get("{a}/{b}", "alpha", "bravo").requestAttr( - TemplateEngine.class.getName(), - new MustacheTemplateEngine(resolver)))); + .description("two").attributes(key("foo").value("bravo")))) + .document(new OperationBuilder( + "path-parameters-with-custom-descriptor-attributes") + .attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).build()); } @Test @@ -147,11 +146,10 @@ public class PathParametersSnippetTests { parameterWithName("a").description("one").attributes( key("foo").value("alpha")), parameterWithName("b") .description("two").attributes(key("foo").value("bravo")))) - .document( - "path-parameters-with-custom-attributes", - result(get("{a}/{b}", "alpha", "bravo").requestAttr( - TemplateEngine.class.getName(), - new MustacheTemplateEngine(resolver)))); + .document(new OperationBuilder("path-parameters-with-custom-attributes") + .attribute("org.springframework.restdocs.urlTemplate", "/{a}/{b}") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).build()); } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java index fd74544d..0ec85773 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java @@ -24,8 +24,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.paramete import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; -import static org.springframework.restdocs.test.TestRequestBuilders.get; import java.io.IOException; import java.util.Arrays; @@ -35,13 +33,12 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.restdocs.config.RestDocumentationContext; import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; +import org.springframework.restdocs.test.OperationBuilder; /** * Tests for {@link RequestParametersSnippet} @@ -63,7 +60,8 @@ public class RequestParametersSnippetTests { .expectMessage(equalTo("Request parameters with the following names were" + " not documented: [a]")); new RequestParametersSnippet(Collections. emptyList()) - .document("undocumented-parameter", result(get("/").param("a", "alpha"))); + .document(new OperationBuilder("undocumented-parameter") + .request("http://localhost").param("a", "alpha").build()); } @Test @@ -73,7 +71,8 @@ public class RequestParametersSnippetTests { .expectMessage(equalTo("Request parameters with the following names were" + " not found in the request: [a]")); new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description( - "one"))).document("missing-parameter", result(get("/"))); + "one"))).document(new OperationBuilder("missing-parameter").request( + "http://localhost").build()); } @Test @@ -84,36 +83,21 @@ public class RequestParametersSnippetTests { + " not documented: [b]. Request parameters with the following" + " names were not found in the request: [a]")); new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description( - "one"))).document("undocumented-and-missing-parameters", result(get("/") - .param("b", "bravo"))); + "one"))).document(new OperationBuilder( + "undocumented-and-missing-parameters").request("http://localhost") + .param("b", "bravo").build()); } @Test - public void requestParameterSnippetFromRequestParameters() throws IOException { - this.snippet.expectRequestParameters( - "request-parameter-snippet-request-parameters").withContents( + public void requestParameters() throws IOException { + this.snippet.expectRequestParameters("request-parameters").withContents( tableWithHeader("Parameter", "Description").row("a", "one").row("b", "two")); new RequestParametersSnippet(Arrays.asList( parameterWithName("a").description("one"), parameterWithName("b") - .description("two"))).document( - "request-parameter-snippet-request-parameters", - result(get("/").param("a", "bravo").param("b", "bravo"))); - } - - @Test - public void requestParameterSnippetFromRequestUriQueryString() throws IOException { - this.snippet.expectRequestParameters( - "request-parameter-snippet-request-uri-query-string").withContents( - tableWithHeader("Parameter", "Description").row("a", "one").row("b", - "two")); - new RequestParametersSnippet(Arrays.asList( - parameterWithName("a").description("one"), parameterWithName("b") - .description("two"))).document( - "request-parameter-snippet-request-uri-query-string", - result(get("/?a=alpha&b=bravo").requestAttr( - RestDocumentationContext.class.getName(), - new RestDocumentationContext(null)))); + .description("two"))).document(new OperationBuilder( + "request-parameters").request("http://localhost").param("a", "bravo") + .param("b", "bravo").build()); } @Test @@ -125,17 +109,15 @@ public class RequestParametersSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("request-parameters")).thenReturn( snippetResource("request-parameters-with-extra-column")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.addParameter("a", "bravo"); - request.addParameter("b", "bravo"); new RequestParametersSnippet(Arrays.asList( parameterWithName("a").description("one").attributes( key("foo").value("alpha")), parameterWithName("b").description("two").attributes( - key("foo").value("bravo")))).document( - "request-parameters-with-custom-descriptor-attributes", result(request)); + key("foo").value("bravo")))).document(new OperationBuilder( + "request-parameters-with-custom-descriptor-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)).request("http://localhost") + .param("a", "alpha").param("b", "bravo").build()); } @Test @@ -145,18 +127,18 @@ public class RequestParametersSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); when(resolver.resolveTemplateResource("request-parameters")).thenReturn( snippetResource("request-parameters-with-title")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.addParameter("a", "bravo"); - request.addParameter("b", "bravo"); new RequestParametersSnippet( attributes(key("title").value("The title")), Arrays.asList( parameterWithName("a").description("one").attributes( key("foo").value("alpha")), parameterWithName("b") .description("two").attributes(key("foo").value("bravo")))) - .document("request-parameters-with-custom-attributes", result(request)); + .document(new OperationBuilder( + "request-parameters-with-custom-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)) + .request("http://localhost").param("a", "alpha") + .param("b", "bravo").build()); } private FileSystemResource snippetResource(String name) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java index dd07830a..09487a3d 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/response/ContentModifyingResponsePostProcessorTests.java @@ -38,6 +38,7 @@ public class ContentModifyingResponsePostProcessorTests { public void contentCanBeModified() throws Exception { MockHttpServletResponse modified = this.postProcessor.postProcess(this.original); assertThat(modified.getContentAsString(), is(equalTo("modified"))); + assertThat(modified.getContentAsByteArray(), is(equalTo("modified".getBytes()))); } @Test @@ -47,14 +48,6 @@ public class ContentModifyingResponsePostProcessorTests { 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 { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/OperationBuilder.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/OperationBuilder.java new file mode 100644 index 00000000..fbb19337 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/OperationBuilder.java @@ -0,0 +1,211 @@ +package org.springframework.restdocs.test; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.restdocs.config.RestDocumentationContext; +import org.springframework.restdocs.config.RestDocumentationContextPlaceholderResolver; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.operation.StandardOperation; +import org.springframework.restdocs.operation.StandardOperationRequest; +import org.springframework.restdocs.operation.StandardOperationRequestPart; +import org.springframework.restdocs.operation.StandardOperationResponse; +import org.springframework.restdocs.snippet.StandardWriterResolver; +import org.springframework.restdocs.snippet.WriterResolver; +import org.springframework.restdocs.templates.StandardTemplateResourceResolver; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; + +public class OperationBuilder { + + private final Map attributes = new HashMap<>(); + + private final OperationResponseBuilder responseBuilder = new OperationResponseBuilder(); + + private final String name; + + private OperationRequestBuilder requestBuilder; + + public OperationBuilder(String name) { + this.name = name; + } + + public OperationRequestBuilder request(String uri) { + this.requestBuilder = new OperationRequestBuilder(uri); + return this.requestBuilder; + } + + public OperationResponseBuilder response() { + return this.responseBuilder; + } + + public OperationBuilder attribute(String name, Object value) { + this.attributes.put(name, value); + return this; + } + + public Operation build() { + if (this.attributes.get(TemplateEngine.class.getName()) == null) { + this.attributes.put(TemplateEngine.class.getName(), + new MustacheTemplateEngine(new StandardTemplateResourceResolver())); + } + RestDocumentationContext context = new RestDocumentationContext(null); + this.attributes.put(RestDocumentationContext.class.getName(), context); + this.attributes.put(WriterResolver.class.getName(), new StandardWriterResolver( + new RestDocumentationContextPlaceholderResolver(context))); + return new StandardOperation(this.name, + (this.requestBuilder == null ? new OperationRequestBuilder( + "http://localhost/").buildRequest() : this.requestBuilder + .buildRequest()), + this.responseBuilder.buildResponse(), this.attributes); + } + + public class OperationRequestBuilder { + + private URI requestUri = URI.create("http://localhost/"); + + private HttpMethod method = HttpMethod.GET; + + private byte[] content = new byte[0]; + + private HttpHeaders headers = new HttpHeaders(); + + private Parameters parameters = new Parameters(); + + private List partBuilders = new ArrayList<>(); + + public OperationRequestBuilder(String uri) { + this.requestUri = URI.create(uri); + } + + private OperationRequest buildRequest() { + List parts = new ArrayList<>(); + for (OperationRequestPartBuilder builder : this.partBuilders) { + parts.add(builder.buildPart()); + } + return new StandardOperationRequest(this.requestUri, this.method, + this.content, this.headers, this.parameters, parts); + } + + public Operation build() { + return OperationBuilder.this.build(); + } + + public OperationRequestBuilder method(String method) { + this.method = HttpMethod.valueOf(method); + return this; + } + + public OperationRequestBuilder content(String content) { + this.content = content.getBytes(); + return this; + } + + public OperationRequestBuilder param(String name, String... values) { + for (String value : values) { + this.parameters.add(name, value); + } + return this; + } + + public OperationRequestBuilder header(String name, String value) { + this.headers.add(name, value); + return this; + } + + public OperationRequestPartBuilder part(String name, byte[] content) { + OperationRequestPartBuilder partBuilder = new OperationRequestPartBuilder( + name, content); + this.partBuilders.add(partBuilder); + return partBuilder; + } + + public class OperationRequestPartBuilder { + + private final String name; + + private final byte[] content; + + private String submittedFileName; + + private HttpHeaders headers = new HttpHeaders(); + + private OperationRequestPartBuilder(String name, byte[] content) { + this.name = name; + this.content = content; + } + + public OperationRequestPartBuilder submittedFileName(String submittedFileName) { + this.submittedFileName = submittedFileName; + return this; + } + + public OperationRequestBuilder and() { + return OperationRequestBuilder.this; + } + + public Operation build() { + return OperationBuilder.this.build(); + } + + private OperationRequestPart buildPart() { + return new StandardOperationRequestPart(this.name, + this.submittedFileName, this.content, this.headers); + } + + public OperationRequestPartBuilder header(String name, String value) { + this.headers.add(name, value); + return this; + } + } + } + + public class OperationResponseBuilder { + + private HttpStatus status = HttpStatus.OK; + + private HttpHeaders headers = new HttpHeaders(); + + private byte[] content = new byte[0]; + + private OperationResponse buildResponse() { + return new StandardOperationResponse(this.status, this.headers, this.content); + } + + public OperationResponseBuilder status(int status) { + this.status = HttpStatus.valueOf(status); + return this; + } + + public OperationResponseBuilder header(String name, String value) { + this.headers.add(name, value); + return this; + } + + public OperationResponseBuilder content(byte[] content) { + this.content = content; + return this; + } + + public OperationResponseBuilder content(String content) { + this.content = content.getBytes(); + return this; + } + + public Operation build() { + return OperationBuilder.this.build(); + } + + } + +}