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
This commit is contained in:
Andy Wilkinson
2016-02-15 15:41:09 +00:00
parent 4d44401efa
commit 40201e2e99
7 changed files with 154 additions and 20 deletions

View File

@@ -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<String> values = parameters.get(components[0]);
if (values == null) {
parameters.put(components[0], new LinkedList<String>());
}
}
}
else {
throw new IllegalArgumentException(

View File

@@ -127,10 +127,16 @@ public class HttpRequestSnippet extends TemplatedSnippet {
private void writeParts(OperationRequest request, PrintWriter writer) {
writer.println();
for (Entry<String, List<String>> 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()) {

View File

@@ -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<String, String> {
public String toQueryString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, List<String>> 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");

View File

@@ -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")

View File

@@ -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(

View File

@@ -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(

View File

@@ -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.<String>emptyList());
}
return this;
}