/*
* Copyright 2013 Netflix, Inc.
*
* 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 feign;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.RequestTemplate.expand;
import static feign.assertj.FeignAssertions.assertThat;
import static java.util.Arrays.asList;
import static org.assertj.core.data.MapEntry.entry;
public class RequestTemplateTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
/**
* Avoid depending on guava solely for map literals.
*/
private static <K, V> Map<K, V> mapOf(K key, V val) {
Map<K, V> result = new LinkedHashMap<K, V>();
result.put(key, val);
return result;
}
private static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2) {
Map<K, V> result = mapOf(k1, v1);
result.put(k2, v2);
return result;
}
private static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3) {
Map<K, V> result = mapOf(k1, v1, k2, v2);
result.put(k3, v3);
return result;
}
@Test
public void expandNotUrlEncoded() {
for (String val : Arrays.asList("apples", "sp ace", "unic???de", "qu?stion")) {
assertThat(expand("/users/{user}", mapOf("user", val)))
.isEqualTo("/users/" + val);
}
}
@Test
public void expandMultipleParams() {
assertThat(expand("/users/{user}/{repo}", mapOf("user", "unic???de", "repo", "foo")))
.isEqualTo("/users/unic???de/foo");
}
@Test
public void expandParamKeyHyphen() {
assertThat(expand("/{user-dir}", mapOf("user-dir", "foo")))
.isEqualTo("/foo");
}
@Test
public void expandMissingParamProceeds() {
assertThat(expand("/{user-dir}", mapOf("user_dir", "foo")))
.isEqualTo("/{user-dir}");
}
@Test
public void resolveTemplateWithParameterizedPathSkipsEncodingSlash() {
RequestTemplate template = new RequestTemplate().method("GET")
.append("{zoneId}");
template.resolve(mapOf("zoneId", "/hostedzone/Z1PA6795UKMFR9"));
assertThat(template)
.hasUrl("/hostedzone/Z1PA6795UKMFR9");
}
@Test
public void canInsertAbsoluteHref() {
RequestTemplate template = new RequestTemplate().method("GET")
.append("/hostedzone/Z1PA6795UKMFR9");
template.insert(0, "https://route53.amazonaws.com/2012-12-12");
assertThat(template)
.hasUrl("https://route53.amazonaws.com/2012-12-12/hostedzone/Z1PA6795UKMFR9");
}
@Test
public void resolveTemplateWithBaseAndParameterizedQuery() {
RequestTemplate template = new RequestTemplate().method("GET")
.append("/?Action=DescribeRegions").query("RegionName.1", "{region}");
template.resolve(mapOf("region", "eu-west-1"));
assertThat(template)
.hasQueries(
entry("Action", asList("DescribeRegions")),
entry("RegionName.1", asList("eu-west-1"))
);
}
@Test
public void resolveTemplateWithBaseAndParameterizedIterableQuery() {
RequestTemplate template = new RequestTemplate().method("GET")
.append("/?Query=one").query("Queries", "{queries}");
template.resolve(mapOf("queries", Arrays.asList("us-east-1", "eu-west-1")));
assertThat(template)
.hasQueries(
entry("Query", asList("one")),
entry("Queries", asList("us-east-1", "eu-west-1"))
);
}
@Test
public void resolveTemplateWithHeaderSubstitutions() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Auth-Token", "{authToken}");
template.resolve(mapOf("authToken", "1234"));
assertThat(template)
.hasHeaders(entry("Auth-Token", asList("1234")));
}
@Test
public void resolveTemplateWithHeaderSubstitutionsNotAtStart() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Authorization", "Bearer {token}");
template.resolve(mapOf("token", "1234"));
assertThat(template)
.hasHeaders(entry("Authorization", asList("Bearer 1234")));
}
@Test
public void resolveTemplateWithHeaderWithEscapedCurlyBrace() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Encoded", "{{{{dont_expand_me}}");
template.resolve(mapOf("dont_expand_me", "1234"));
assertThat(template)
.hasHeaders(entry("Encoded", asList("{{dont_expand_me}}")));
}
/** This ensures we don't mess up vnd types */
@Test
public void resolveTemplateWithHeaderIncludingSpecialCharacters() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Accept", "application/vnd.github.v3+{type}");
template.resolve(mapOf("type", "json"));
assertThat(template)
.hasHeaders(entry("Accept", asList("application/vnd.github.v3+json")));
}
@Test
public void resolveTemplateWithHeaderEmptyResult() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Encoded", "{var}");
template.resolve(mapOf("var", ""));
assertThat(template)
.hasHeaders(entry("Encoded", asList("")));
}
@Test
public void resolveTemplateWithMixedRequestLineParams() throws Exception {
RequestTemplate template = new RequestTemplate().method("GET")//
.append("/domains/{domainId}/records")//
.query("name", "{name}")//
.query("type", "{type}");
template = template.resolve(
mapOf("domainId", 1001, "name", "denominator.io", "type", "CNAME")
);
assertThat(template)
.hasUrl("/domains/1001/records")
.hasQueries(
entry("name", asList("denominator.io")),
entry("type", asList("CNAME"))
);
}
@Test
public void insertHasQueryParams() throws Exception {
RequestTemplate template = new RequestTemplate().method("GET")//
.append("/domains/1001/records")//
.query("name", "denominator.io")//
.query("type", "CNAME");
template.insert(0, "https://host/v1.0/1234?provider=foo");
assertThat(template)
.hasUrl("https://host/v1.0/1234/domains/1001/records")
.hasQueries(
entry("provider", asList("foo")),
entry("name", asList("denominator.io")),
entry("type", asList("CNAME"))
);
}
@Test
public void resolveTemplateWithBodyTemplateSetsBodyAndContentLength() {
RequestTemplate template = new RequestTemplate().method("POST")
.bodyTemplate(
"%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", " +
"\"password\": \"{password}\"%7D");
template = template.resolve(
mapOf(
"customer_name", "netflix",
"user_name", "denominator",
"password", "password"
)
);
assertThat(template)
.hasBody(
"{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}")
.hasHeaders(
entry("Content-Length", asList(String.valueOf(template.body().length)))
);
}
@Test
public void resolveTemplateWithBodyTemplateDoesNotDoubleDecode() {
RequestTemplate template = new RequestTemplate().method("POST")
.bodyTemplate(
"%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D");
template = template.resolve(
mapOf(
"customer_name", "netflix",
"user_name", "denominator",
"password", "abc+123%25d8"
)
);
assertThat(template)
.hasBody(
"{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"abc+123%25d8\"}"
);
}
@Test
public void skipUnresolvedQueries() throws Exception {
RequestTemplate template = new RequestTemplate().method("GET")//
.append("/domains/{domainId}/records")//
.query("optional", "{optional}")//
.query("name", "{nameVariable}");
template = template.resolve(mapOf(
"domainId", 1001,
"nameVariable", "denominator.io"
)
);
assertThat(template)
.hasUrl("/domains/1001/records")
.hasQueries(
entry("name", asList("denominator.io"))
);
}
@Test
public void allQueriesUnresolvable() throws Exception {
RequestTemplate template = new RequestTemplate().method("GET")//
.append("/domains/{domainId}/records")//
.query("optional", "{optional}")//
.query("optional2", "{optional2}");
template = template.resolve(mapOf("domainId", 1001));
assertThat(template)
.hasUrl("/domains/1001/records")
.hasQueries();
}
@Test
public void spaceEncodingInUrlParam() {
RequestTemplate template = new RequestTemplate().method("GET")//
.append("/api/{value1}?key={value2}");
template = template.resolve(mapOf("value1", "ABC 123", "value2", "XYZ 123"));
assertThat(template.request().url())
.isEqualTo("/api/ABC%20123?key=XYZ+123");
}
@Test
public void encodeSlashTest() throws Exception {
RequestTemplate template = new RequestTemplate().method("GET")
.append("/api/{vhost}")
.decodeSlash(false);
template.resolve(mapOf("vhost", "/"));
assertThat(template)
.hasUrl("/api/%2F");
}
/** Implementations have a bug if they pass junk as the http method. */
@Test
public void uriStuffedIntoMethod() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid HTTP Method: /path?queryParam={queryParam}");
new RequestTemplate().method("/path?queryParam={queryParam}");
}
@Test
public void encodedQueryClearedOnNull() throws Exception {
RequestTemplate template = new RequestTemplate();
template.query("param[]", "value");
assertThat(template).hasQueries(entry("param[]", asList("value")));
template.query("param[]", (String[]) null);
assertThat(template.queries()).isEmpty();
}
@Test
public void encodedQuery() throws Exception {
RequestTemplate template = new RequestTemplate().query(true, "params[]", "foo%20bar");
assertThat(template.queryLine()).isEqualTo("?params[]=foo%20bar");
assertThat(template).hasQueries(entry("params[]", asList("foo bar")));
}
@Test
public void encodedQueryWithUnsafeCharactersMixedWithUnencoded() throws Exception {
RequestTemplate template = new RequestTemplate()
.query(false, "params[]", "not encoded") // stored as "param%5D%5B"
.query(true, "params[]", "encoded"); // stored as "param[]"
// We can't ensure consistent behavior, because decode("param[]") == decode("param%5B%5D")
assertThat(template.queryLine()).isEqualTo("?params%5B%5D=not+encoded¶ms[]=encoded");
assertThat(template.queries()).doesNotContain(entry("params[]", asList("not encoded")));
assertThat(template.queries()).contains(entry("params[]", asList("encoded")));
}
}