/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.net.URI; import java.util.AbstractMap; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.jupiter.api.Test; /** * Verify that {@link DefaultSdkHttpFullRequest} and {@link DefaultSdkHttpFullResponse} properly fulfill the contracts in their * interfaces. */ public class SdkHttpRequestResponseTest { @Test public void optionalValuesAreOptional() { assertThat(validRequest().contentStreamProvider()).isNotPresent(); assertThat(validResponse().content()).isNotPresent(); assertThat(validResponse().statusText()).isNotPresent(); } @Test public void requestHeaderMapsAreCopiedWhenModified() { assertRequestHeaderMapsAreCopied(b -> b.putHeader("foo", "bar")); assertRequestHeaderMapsAreCopied(b -> b.putHeader("foo", singletonList("bar"))); assertRequestHeaderMapsAreCopied(b -> b.appendHeader("foo", "bar")); assertRequestHeaderMapsAreCopied(b -> b.headers(emptyMap())); assertRequestHeaderMapsAreCopied(b -> b.clearHeaders()); assertRequestHeaderMapsAreCopied(b -> b.removeHeader("Accept")); } @Test public void requestQueryStringMapsAreCopiedWhenModified() { assertRequestQueryStringMapsAreCopied(b -> b.putRawQueryParameter("foo", "bar")); assertRequestQueryStringMapsAreCopied(b -> b.putRawQueryParameter("foo", singletonList("bar"))); assertRequestQueryStringMapsAreCopied(b -> b.appendRawQueryParameter("foo", "bar")); assertRequestQueryStringMapsAreCopied(b -> b.rawQueryParameters(emptyMap())); assertRequestQueryStringMapsAreCopied(b -> b.clearQueryParameters()); assertRequestQueryStringMapsAreCopied(b -> b.removeQueryParameter("Accept")); } @Test public void responseHeaderMapsAreCopiedWhenModified() { assertResponseHeaderMapsAreCopied(b -> b.putHeader("foo", "bar")); assertResponseHeaderMapsAreCopied(b -> b.putHeader("foo", singletonList("bar"))); assertResponseHeaderMapsAreCopied(b -> b.appendHeader("foo", "bar")); assertResponseHeaderMapsAreCopied(b -> b.headers(emptyMap())); assertResponseHeaderMapsAreCopied(b -> b.clearHeaders()); assertResponseHeaderMapsAreCopied(b -> b.removeHeader("Accept")); } private void assertRequestHeaderMapsAreCopied(Consumer mutation) { SdkHttpFullRequest request = validRequestWithMaps(); Map> originalQuery = new LinkedHashMap<>(request.headers()); SdkHttpFullRequest.Builder builder = request.toBuilder(); assertThat(request.headers()).isEqualTo(builder.headers()); builder.applyMutation(mutation); SdkHttpFullRequest request2 = builder.build(); assertThat(request.headers()).isEqualTo(originalQuery); assertThat(request.headers()).isNotEqualTo(request2.headers()); } private void assertRequestQueryStringMapsAreCopied(Consumer mutation) { SdkHttpFullRequest request = validRequestWithMaps(); Map> originalQuery = new LinkedHashMap<>(request.rawQueryParameters()); SdkHttpFullRequest.Builder builder = request.toBuilder(); assertThat(request.rawQueryParameters()).isEqualTo(builder.rawQueryParameters()); builder.applyMutation(mutation); SdkHttpFullRequest request2 = builder.build(); assertThat(request.rawQueryParameters()).isEqualTo(originalQuery); assertThat(request.rawQueryParameters()).isNotEqualTo(request2.rawQueryParameters()); } private void assertResponseHeaderMapsAreCopied(Consumer mutation) { SdkHttpResponse response = validResponseWithMaps(); Map> originalQuery = new LinkedHashMap<>(response.headers()); SdkHttpResponse.Builder builder = response.toBuilder(); assertThat(response.headers()).isEqualTo(builder.headers()); builder.applyMutation(mutation); SdkHttpResponse response2 = builder.build(); assertThat(response.headers()).isEqualTo(originalQuery); assertThat(response.headers()).isNotEqualTo(response2.headers()); } @Test public void headersAreUnmodifiable() { assertThatThrownBy(() -> validRequest().headers().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validResponse().headers().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validRequest().toBuilder().headers().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validResponse().toBuilder().headers().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validRequest().toBuilder().build().headers().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validResponse().toBuilder().build().headers().clear()).isInstanceOf(UnsupportedOperationException.class); } @Test public void queryStringsAreUnmodifiable() { assertThatThrownBy(() -> validRequest().rawQueryParameters().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validRequest().toBuilder().rawQueryParameters().clear()).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> validRequest().toBuilder().build().rawQueryParameters().clear()).isInstanceOf(UnsupportedOperationException.class); } @Test public void uriConversionIsCorrect() { assertThat(normalizedUri(b -> b.protocol("http").host("localhost"))).isEqualTo("http://localhost"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80))).isEqualTo("http://localhost"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(8080))).isEqualTo("http://localhost:8080"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(443))).isEqualTo("http://localhost:443"); assertThat(normalizedUri(b -> b.protocol("https").host("localhost").port(443))).isEqualTo("https://localhost"); assertThat(normalizedUri(b -> b.protocol("https").host("localhost").port(8443))).isEqualTo("https://localhost:8443"); assertThat(normalizedUri(b -> b.protocol("https").host("localhost").port(80))).isEqualTo("https://localhost:80"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("foo"))) .isEqualTo("http://localhost/foo"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("/foo"))) .isEqualTo("http://localhost/foo"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("foo/"))) .isEqualTo("http://localhost/foo/"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("/foo/"))) .isEqualTo("http://localhost/foo/"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).putRawQueryParameter("foo", "bar "))) .isEqualTo("http://localhost?foo=bar%20"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("/foo").putRawQueryParameter("foo", "bar"))) .isEqualTo("http://localhost/foo?foo=bar"); assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("foo/").putRawQueryParameter("foo", "bar"))) .isEqualTo("http://localhost/foo/?foo=bar"); } private String normalizedUri(Consumer builderMutator) { return validRequestBuilder().applyMutation(builderMutator).build().getUri().toString(); } @Test public void protocolNormalizationIsCorrect() { assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> normalizedProtocol(null)); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> normalizedProtocol("foo")); assertThat(normalizedProtocol("http")).isEqualTo("http"); assertThat(normalizedProtocol("https")).isEqualTo("https"); assertThat(normalizedProtocol("HtTp")).isEqualTo("http"); assertThat(normalizedProtocol("HtTpS")).isEqualTo("https"); } private String normalizedProtocol(String protocol) { return validRequestBuilder().protocol(protocol).build().protocol(); } @Test public void hostNormalizationIsCorrect() { assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> normalizedHost(null)); assertThat(normalizedHost("foo")).isEqualTo("foo"); } private String normalizedHost(String host) { return validRequestBuilder().host(host).build().host(); } @Test public void portNormalizationIsCorrect() { assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> normalizedPort("http", -2)); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> normalizedPort("https", -2)); assertThat(normalizedPort("http", -1)).isEqualTo(80); assertThat(normalizedPort("http", null)).isEqualTo(80); assertThat(normalizedPort("https", -1)).isEqualTo(443); assertThat(normalizedPort("https", null)).isEqualTo(443); assertThat(normalizedPort("http", 8080)).isEqualTo(8080); assertThat(normalizedPort("https", 8443)).isEqualTo(8443); } private int normalizedPort(String protocol, Integer port) { return validRequestBuilder().protocol(protocol).port(port).build().port(); } @Test public void requestPathNormalizationIsCorrect() { assertThat(normalizedPath(null)).isEqualTo(""); assertThat(normalizedPath("/")).isEqualTo("/"); assertThat(normalizedPath(" ")).isEqualTo("/ "); assertThat(normalizedPath(" /")).isEqualTo("/ /"); assertThat(normalizedPath("/ ")).isEqualTo("/ "); assertThat(normalizedPath("/ /")).isEqualTo("/ /"); assertThat(normalizedPath(" / ")).isEqualTo("/ / "); assertThat(normalizedPath("/Foo/")).isEqualTo("/Foo/"); assertThat(normalizedPath("Foo/")).isEqualTo("/Foo/"); assertThat(normalizedPath("Foo")).isEqualTo("/Foo"); assertThat(normalizedPath("/Foo/bar/")).isEqualTo("/Foo/bar/"); assertThat(normalizedPath("Foo/bar/")).isEqualTo("/Foo/bar/"); assertThat(normalizedPath("/Foo/bar")).isEqualTo("/Foo/bar"); assertThat(normalizedPath("Foo/bar")).isEqualTo("/Foo/bar"); assertThat(normalizedPath("%2F")).isEqualTo("/%2F"); } private String normalizedPath(String path) { return validRequestBuilder().encodedPath(path).build().encodedPath(); } @Test public void requestMethodNormalizationIsCorrect() { assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> normalizedMethod(null)); assertThat(normalizedMethod(SdkHttpMethod.POST)).isEqualTo(SdkHttpMethod.POST); } private SdkHttpMethod normalizedMethod(SdkHttpMethod method) { return validRequestBuilder().method(method).build().method(); } @Test public void requestQueryParamNormalizationIsCorrect() { headerOrQueryStringNormalizationIsCorrect(() -> new BuilderProxy() { private final SdkHttpRequest.Builder builder = validRequestBuilder(); @Override public BuilderProxy setValue(String key, String value) { builder.putRawQueryParameter(key, value); return this; } @Override public BuilderProxy appendValue(String key, String value) { builder.appendRawQueryParameter(key, value); return this; } @Override public BuilderProxy setValues(String key, List values) { builder.putRawQueryParameter(key, values); return this; } @Override public BuilderProxy removeValue(String key) { builder.removeQueryParameter(key); return this; } @Override public BuilderProxy clearValues() { builder.clearQueryParameters(); return this; } @Override public BuilderProxy setMap(Map> map) { builder.rawQueryParameters(map); return this; } @Override public Map> getMap() { return builder.build().rawQueryParameters(); } }); } @Test public void requestHeaderNormalizationIsCorrect() { headerOrQueryStringNormalizationIsCorrect(() -> new BuilderProxy() { private final SdkHttpRequest.Builder builder = validRequestBuilder(); @Override public BuilderProxy setValue(String key, String value) { builder.putHeader(key, value); return this; } @Override public BuilderProxy appendValue(String key, String value) { builder.appendHeader(key, value); return this; } @Override public BuilderProxy setValues(String key, List values) { builder.putHeader(key, values); return this; } @Override public BuilderProxy removeValue(String key) { builder.removeHeader(key); return this; } @Override public BuilderProxy clearValues() { builder.clearHeaders(); return this; } @Override public BuilderProxy setMap(Map> map) { builder.headers(map); return this; } @Override public Map> getMap() { return builder.build().headers(); } }); } @Test public void responseStatusCodeNormalizationIsCorrect() { assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> normalizedStatusCode(-1)); assertThat(normalizedStatusCode(200)).isEqualTo(200); } private int normalizedStatusCode(int statusCode) { return validResponseBuilder().statusCode(statusCode).build().statusCode(); } @Test public void responseHeaderNormalizationIsCorrect() { headerOrQueryStringNormalizationIsCorrect(() -> new BuilderProxy() { private final SdkHttpFullResponse.Builder builder = validResponseBuilder(); @Override public BuilderProxy setValue(String key, String value) { builder.putHeader(key, value); return this; } @Override public BuilderProxy appendValue(String key, String value) { builder.appendHeader(key, value); return this; } @Override public BuilderProxy setValues(String key, List values) { builder.putHeader(key, values); return this; } @Override public BuilderProxy removeValue(String key) { builder.removeHeader(key); return this; } @Override public BuilderProxy clearValues() { builder.clearHeaders(); return this; } @Override public BuilderProxy setMap(Map> map) { builder.headers(map); return this; } @Override public Map> getMap() { return builder.build().headers(); } }); } @Test public void testSdkHttpFullRequestBuilderNoQueryParams() { URI uri = URI.create("https://github.com/aws/aws-sdk-java-v2/issues/2034"); final SdkHttpFullRequest sdkHttpFullRequest = SdkHttpFullRequest.builder().method(SdkHttpMethod.POST).uri(uri).build(); assertThat(sdkHttpFullRequest.getUri().getQuery()).isNullOrEmpty(); } @Test public void testSdkHttpFullRequestBuilderUriWithQueryParams() { URI uri = URI.create("https://github.com/aws/aws-sdk-java-v2/issues/2034?reqParam=1234&oParam=3456%26reqParam%3D5678"); final SdkHttpFullRequest sdkHttpFullRequest = SdkHttpFullRequest.builder().method(SdkHttpMethod.POST).uri(uri).build(); assertThat(sdkHttpFullRequest.getUri().getQuery()).contains("reqParam=1234", "oParam=3456&reqParam=5678"); } @Test public void testSdkHttpFullRequestBuilderUriWithQueryParamWithoutValue() { final String expected = "https://github.com/aws/aws-sdk-for-java-v2?foo"; URI myUri = URI.create(expected); SdkHttpFullRequest actual = SdkHttpFullRequest.builder().method(SdkHttpMethod.POST).uri(myUri).build(); assertThat(actual.getUri()).hasToString(expected); } @Test public void testSdkHttpRequestBuilderNoQueryParams() { URI uri = URI.create("https://github.com/aws/aws-sdk-java-v2/issues/2034"); final SdkHttpRequest sdkHttpRequest = SdkHttpRequest.builder().method(SdkHttpMethod.POST).uri(uri).build(); assertThat(sdkHttpRequest.getUri().getQuery()).isNullOrEmpty(); } @Test public void testSdkHttpRequestBuilderUriWithQueryParams() { URI uri = URI.create("https://github.com/aws/aws-sdk-java-v2/issues/2034?reqParam=1234&oParam=3456%26reqParam%3D5678"); final SdkHttpRequest sdkHttpRequest = SdkHttpRequest.builder().method(SdkHttpMethod.POST).uri(uri).build(); assertThat(sdkHttpRequest.getUri().getQuery()).contains("reqParam=1234", "oParam=3456&reqParam=5678"); } @Test public void testSdkHttpRequestBuilderUriWithQueryParamsIgnoreOtherQueryParams() { URI uri = URI.create("https://github.com/aws/aws-sdk-java-v2/issues/2034?reqParam=1234&oParam=3456%26reqParam%3D5678"); final SdkHttpRequest sdkHttpRequest = SdkHttpRequest.builder().method(SdkHttpMethod.POST).appendRawQueryParameter("clean", "up").uri(uri).build(); assertThat(sdkHttpRequest.getUri().getQuery()).contains("reqParam=1234", "oParam=3456&reqParam=5678") .doesNotContain("clean"); } @Test public void testSdkHttpRequestBuilderUriWithQueryParamWithoutValue() { final String expected = "https://github.com/aws/aws-sdk-for-java-v2?foo"; URI myUri = URI.create(expected); SdkHttpRequest actual = SdkHttpRequest.builder().method(SdkHttpMethod.POST).uri(myUri).build(); assertThat(actual.getUri()).hasToString(expected); } private interface BuilderProxy { BuilderProxy setValue(String key, String value); BuilderProxy appendValue(String key, String value); BuilderProxy setValues(String key, List values); BuilderProxy removeValue(String key); BuilderProxy clearValues(); BuilderProxy setMap(Map> map); Map> getMap(); } private void headerOrQueryStringNormalizationIsCorrect(Supplier builderFactory) { assertMapIsInitiallyEmpty(builderFactory); setValue_SetsSingleValueCorrectly(builderFactory); setValue_SettingMultipleKeysAppendsToMap(builderFactory); setValue_OverwritesExistingValue(builderFactory); setValues_SetsAllValuesCorrectly(builderFactory); setValue_OverwritesAllExistingValues(builderFactory); removeValue_OnlyRemovesThatKey(builderFactory); clearValues_RemovesAllExistingValues(builderFactory); setMap_OverwritesAllExistingValues(builderFactory); appendWithExistingValues_AddsValueToList(builderFactory); appendWithNoValues_AddsSingleElementToList(builderFactory); } private void assertMapIsInitiallyEmpty(Supplier builderFactory) { assertThat(builderFactory.get().setMap(emptyMap()).getMap()).isEmpty(); } private void setValue_SetsSingleValueCorrectly(Supplier builderFactory) { assertThat(builderFactory.get().setValue("Foo", "Bar").getMap()).satisfies(params -> { assertThat(params).containsOnlyKeys("Foo"); assertThat(params.get("Foo")).containsExactly("Bar"); }); } private void setValue_SettingMultipleKeysAppendsToMap(Supplier builderFactory) { assertThat(builderFactory.get().setValue("Foo", "Bar").setValue("Foo2", "Bar2").getMap()).satisfies(params -> { assertThat(params).containsOnlyKeys("Foo", "Foo2"); assertThat(params.get("Foo")).containsExactly("Bar"); assertThat(params.get("Foo2")).containsExactly("Bar2"); }); } private void setValue_OverwritesExistingValue(Supplier builderFactory) { assertThat(builderFactory.get().setValue("Foo", "Bar").setValue("Foo", "Bar2").getMap()).satisfies(params -> { assertThat(params).containsOnlyKeys("Foo"); assertThat(params.get("Foo")).containsExactly("Bar2"); }); } private void setValues_SetsAllValuesCorrectly(Supplier builderFactory) { assertThat(builderFactory.get().setValues("Foo", Arrays.asList("Bar", "Baz")).getMap()).satisfies(params -> { assertThat(params).containsOnlyKeys("Foo"); assertThat(params.get("Foo")).containsExactly("Bar", "Baz"); }); } private void setValue_OverwritesAllExistingValues(Supplier builderFactory) { assertThat(builderFactory.get().setValues("Foo", Arrays.asList("Bar", "Baz")).setValue("Foo", "Bar").getMap()) .satisfies(params -> { assertThat(params).containsOnlyKeys("Foo"); assertThat(params.get("Foo")).containsExactly("Bar"); }); } private void removeValue_OnlyRemovesThatKey(Supplier builderFactory) { assertThat(builderFactory.get().setValues("Foo", Arrays.asList("Bar", "Baz")) .setValues("Foo2", Arrays.asList("Bar", "Baz")) .removeValue("Foo").getMap()) .satisfies(params -> { assertThat(params).doesNotContainKeys("Foo"); assertThat(params.get("Foo2")).containsExactly("Bar", "Baz"); }); } private void clearValues_RemovesAllExistingValues(Supplier builderFactory) { assertThat(builderFactory.get().setValues("Foo", Arrays.asList("Bar", "Baz")).clearValues().getMap()) .doesNotContainKeys("Foo"); } private void setMap_OverwritesAllExistingValues(Supplier builderFactory) { assertThat(builderFactory.get().setValues("Foo", Arrays.asList("Bar", "Baz")) .setMap(singletonMap("Foo2", singletonList("Baz2"))).getMap()) .satisfies(params -> { assertThat(params).containsOnlyKeys("Foo2"); assertThat(params.get("Foo2")).containsExactly("Baz2"); }); } private void appendWithExistingValues_AddsValueToList(Supplier builderFactory) { assertThat(builderFactory.get().setValues("Key", Arrays.asList("Foo", "Bar")) .appendValue("Key", "Baz").getMap()) .satisfies(params -> { assertThat(params).containsOnly(new AbstractMap.SimpleEntry<>("Key", Arrays.asList("Foo", "Bar", "Baz"))); }); } private void appendWithNoValues_AddsSingleElementToList(Supplier builderFactory) { assertThat(builderFactory.get().appendValue("AppendNotExists", "Baz").getMap()) .satisfies(params -> { assertThat(params).containsOnly(new AbstractMap.SimpleEntry<>("AppendNotExists", Arrays.asList("Baz"))); }); } private SdkHttpFullRequest validRequestWithMaps() { return validRequestWithMapsBuilder().build(); } private SdkHttpFullRequest.Builder validRequestWithMapsBuilder() { return validRequestBuilder().putHeader("Accept", "*/*") .putRawQueryParameter("Accept", "*/*"); } private SdkHttpFullRequest validRequest() { return validRequestBuilder().build(); } private SdkHttpFullRequest.Builder validRequestBuilder() { return SdkHttpFullRequest.builder() .protocol("http") .host("localhost") .method(SdkHttpMethod.GET); } private SdkHttpResponse validResponseWithMaps() { return validResponseWithMapsBuilder().build(); } private SdkHttpResponse.Builder validResponseWithMapsBuilder() { return validResponseBuilder().putHeader("Accept", "*/*"); } private SdkHttpFullResponse validResponse() { return validResponseBuilder().build(); } private SdkHttpFullResponse.Builder validResponseBuilder() { return SdkHttpFullResponse.builder().statusCode(500); } }