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; }