From 40201e2e994d7a3acd2c15f91b96574e6eb6579a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 15 Feb 2016 15:41:09 +0000 Subject: [PATCH] Improve handling of empty parameters in curl and HTTP request snippets Previously, both the curl and HTTP request snippets would ignore a parameter with no value, for example from the query string of the url http://localhost:8080/foo?bar. This commit updates both snippets so that such parameters are included in the generated snippet, including a multi-part request that is uploading form data and a field in the form has no value. Additions have been made to the tests for both snippets. While the request parameters snippet correctly handled parameters with no value, there was no test verifying that this was the case. One has been added in this commit. Closes gh-200 --- .../restdocs/curl/QueryStringParser.java | 20 +++++-- .../restdocs/http/HttpRequestSnippet.java | 12 +++-- .../restdocs/operation/Parameters.java | 28 +++++++--- .../curl/CurlRequestSnippetTests.java | 36 ++++++++++++- .../http/HttpRequestSnippetTests.java | 52 ++++++++++++++++++- .../RequestParametersSnippetTests.java | 14 ++++- .../restdocs/test/OperationBuilder.java | 12 +++-- 7 files changed, 154 insertions(+), 20 deletions(-) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java index 0982adf9..ea5f3148 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.springframework.restdocs.curl; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; +import java.util.LinkedList; +import java.util.List; import java.util.Scanner; import org.springframework.restdocs.operation.Parameters; @@ -58,10 +60,18 @@ public class QueryStringParser { private void processParameter(String parameter, Parameters parameters) { String[] components = parameter.split("="); - if (components.length == 2) { - String name = components[0]; - String value = components[1]; - parameters.add(decode(name), decode(value)); + if (components.length > 0 && components.length < 3) { + if (components.length == 2) { + String name = components[0]; + String value = components[1]; + parameters.add(decode(name), decode(value)); + } + else { + List values = parameters.get(components[0]); + if (values == null) { + parameters.put(components[0], new LinkedList()); + } + } } else { throw new IllegalArgumentException( diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java index 38d1e35f..cd855f7b 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java @@ -127,10 +127,16 @@ public class HttpRequestSnippet extends TemplatedSnippet { private void writeParts(OperationRequest request, PrintWriter writer) { writer.println(); for (Entry> parameter : request.getParameters().entrySet()) { - for (String value : parameter.getValue()) { + if (parameter.getValue().isEmpty()) { writePartBoundary(writer); - writePart(parameter.getKey(), value, null, writer); - writer.println(); + writePart(parameter.getKey(), "", null, writer); + } + else { + for (String value : parameter.getValue()) { + writePartBoundary(writer); + writePart(parameter.getKey(), value, null, writer); + writer.println(); + } } } for (OperationRequestPart part : request.getParts()) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java index 9660a981..0235ec80 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,17 +40,33 @@ public class Parameters extends LinkedMultiValueMap { public String toQueryString() { StringBuilder sb = new StringBuilder(); for (Map.Entry> entry : entrySet()) { - for (String value : entry.getValue()) { - if (sb.length() > 0) { - sb.append("&"); + if (entry.getValue().isEmpty()) { + append(sb, entry.getKey()); + } + else { + for (String value : entry.getValue()) { + append(sb, entry.getKey(), value); } - sb.append(urlEncodeUTF8(entry.getKey())).append('=') - .append(urlEncodeUTF8(value)); } } return sb.toString(); } + private static void append(StringBuilder sb, String key) { + append(sb, key, ""); + } + + private static void append(StringBuilder sb, String key, String value) { + doAppend(sb, urlEncodeUTF8(key) + "=" + urlEncodeUTF8(value)); + } + + private static void doAppend(StringBuilder sb, String toAppend) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(toAppend); + } + private static String urlEncodeUTF8(String s) { try { return URLEncoder.encode(s, "UTF-8"); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java index c614cb83..51964433 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,6 +95,17 @@ public class CurlRequestSnippetTests { .request("http://localhost/foo?param=value").build()); } + @Test + public void getRequestWithQueryStringWithNoValue() throws IOException { + this.snippet.expectCurlRequest("request-with-query-string-with-no-value") + .withContents(codeBlock("bash") + .content("$ curl 'http://localhost/foo?param' -i")); + new CurlRequestSnippet() + .document(new OperationBuilder("request-with-query-string-with-no-value", + this.snippet.getOutputDirectory()) + .request("http://localhost/foo?param").build()); + } + @Test public void postRequestWithQueryString() throws IOException { this.snippet.expectCurlRequest("post-request-with-query-string") @@ -107,6 +118,18 @@ public class CurlRequestSnippetTests { .method("POST").build()); } + @Test + public void postRequestWithQueryStringWithNoValue() throws IOException { + this.snippet.expectCurlRequest("post-request-with-query-string-with-no-value") + .withContents(codeBlock("bash") + .content("$ curl 'http://localhost/foo?param' -i -X POST")); + new CurlRequestSnippet().document( + new OperationBuilder("post-request-with-query-string-with-no-value", + this.snippet.getOutputDirectory()) + .request("http://localhost/foo?param").method("POST") + .build()); + } + @Test public void postRequestWithOneParameter() throws IOException { this.snippet.expectCurlRequest("post-request-with-one-parameter") @@ -118,6 +141,17 @@ public class CurlRequestSnippetTests { .method("POST").param("k1", "v1").build()); } + @Test + public void postRequestWithOneParameterWithNoValue() throws IOException { + this.snippet.expectCurlRequest("post-request-with-one-parameter-with-no-value") + .withContents(codeBlock("bash") + .content("$ curl 'http://localhost/foo' -i -X POST -d 'k1='")); + new CurlRequestSnippet().document( + new OperationBuilder("post-request-with-one-parameter-with-no-value", + this.snippet.getOutputDirectory()).request("http://localhost/foo") + .method("POST").param("k1").build()); + } + @Test public void postRequestWithMultipleParameters() throws IOException { this.snippet.expectCurlRequest("post-request-with-multiple-parameters") diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java index a2975674..5175edd8 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,6 +75,18 @@ public class HttpRequestSnippetTests { .request("http://localhost/foo?bar=baz").build()); } + @Test + public void getRequestWithQueryStringWithNoValue() throws IOException { + this.snippet.expectHttpRequest("get-request-with-query-string-with-no-value") + .withContents(httpRequest(RequestMethod.GET, "/foo?bar") + .header(HttpHeaders.HOST, "localhost")); + + new HttpRequestSnippet().document( + new OperationBuilder("get-request-with-query-string-with-no-value", + this.snippet.getOutputDirectory()) + .request("http://localhost/foo?bar").build()); + } + @Test public void postRequestWithContent() throws IOException { String content = "Hello, world"; @@ -123,6 +135,20 @@ public class HttpRequestSnippetTests { .build()); } + @Test + public void postRequestWithParameterWithNoValue() throws IOException { + this.snippet.expectHttpRequest("post-request-with-parameter") + .withContents(httpRequest(RequestMethod.POST, "/foo") + .header(HttpHeaders.HOST, "localhost") + .header("Content-Type", "application/x-www-form-urlencoded") + .content("bar=")); + + new HttpRequestSnippet() + .document(new OperationBuilder("post-request-with-parameter", + this.snippet.getOutputDirectory()).request("http://localhost/foo") + .method("POST").param("bar").build()); + } + @Test public void putRequestWithContent() throws IOException { String content = "Hello, world"; @@ -201,6 +227,30 @@ public class HttpRequestSnippetTests { .part("image", "<< data >>".getBytes()).build()); } + @Test + public void multipartPostWithParameterWithNoValue() throws IOException { + String paramPart = createPart( + String.format("Content-Disposition: form-data; " + "name=a%n"), false); + String filePart = createPart(String + .format("Content-Disposition: form-data; " + "name=image%n%n<< data >>")); + String expectedContent = paramPart + filePart; + this.snippet + .expectHttpRequest( + "multipart-post-with-parameter-with-no-value") + .withContents(httpRequest(RequestMethod.POST, "/upload") + .header("Content-Type", + "multipart/form-data; boundary=" + BOUNDARY) + .header(HttpHeaders.HOST, "localhost").content(expectedContent)); + new HttpRequestSnippet().document( + new OperationBuilder("multipart-post-with-parameter-with-no-value", + this.snippet.getOutputDirectory()) + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.MULTIPART_FORM_DATA_VALUE) + .param("a").part("image", "<< data >>".getBytes()) + .build()); + } + @Test public void multipartPostWithContentType() throws IOException { String expectedContent = createPart( diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java index 548b287c..c4b05f35 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,6 +109,18 @@ public class RequestParametersSnippetTests { .build()); } + @Test + public void requestParameterWithNoValue() throws IOException { + this.snippet.expectRequestParameters("request-parameter-with-no-value") + .withContents( + tableWithHeader("Parameter", "Description").row("a", "one")); + new RequestParametersSnippet( + Arrays.asList(parameterWithName("a").description("one"))) + .document(new OperationBuilder("request-parameter-with-no-value", + this.snippet.getOutputDirectory()) + .request("http://localhost").param("a").build()); + } + @Test public void ignoredRequestParameter() throws IOException { this.snippet.expectRequestParameters("ignored-request-parameter").withContents( diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OperationBuilder.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OperationBuilder.java index 5a71d26c..e483d6c5 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OperationBuilder.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OperationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.springframework.restdocs.test; import java.io.File; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -146,8 +147,13 @@ public class OperationBuilder { } public OperationRequestBuilder param(String name, String... values) { - for (String value : values) { - this.parameters.add(name, value); + if (values.length > 0) { + for (String value : values) { + this.parameters.add(name, value); + } + } + else { + this.parameters.put(name, Collections.emptyList()); } return this; }