diff --git a/pom.xml b/pom.xml index e925ca42..c8095c6a 100644 --- a/pom.xml +++ b/pom.xml @@ -253,6 +253,21 @@ OF THE POSSIBILITY OF SUCH DAMAGE. provided true + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.glassfish + javax.json + provided + @@ -263,6 +278,20 @@ OF THE POSSIBILITY OF SUCH DAMAGE. import pom + + org.testcontainers + testcontainers-bom + 1.15.3 + pom + import + + + org.junit + junit-bom + 5.7.2 + pom + import + diff --git a/src/test/java/com/jcabi/http/RequestITCaseTemplate.java b/src/test/java/com/jcabi/http/RequestITCaseTemplate.java new file mode 100644 index 00000000..8f43acea --- /dev/null +++ b/src/test/java/com/jcabi/http/RequestITCaseTemplate.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2011-2017, jcabi.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: 1) Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. 2) Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. 3) Neither the name of the jcabi.com nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jcabi.http; + +import com.jcabi.aspects.Tv; +import com.jcabi.http.request.JdkRequest; +import com.jcabi.http.response.JsonResponse; +import com.jcabi.http.response.RestResponse; +import com.jcabi.http.response.XmlResponse; +import com.jcabi.http.wire.AutoRedirectingWire; +import com.jcabi.http.wire.BasicAuthWire; +import com.jcabi.http.wire.CookieOptimizingWire; +import com.jcabi.xml.XML; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Locale; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonValue; +import javax.ws.rs.core.HttpHeaders; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Tests for any implementation of {@link Request}. + * + * @checkstyle JavadocMethodCheck (500 lines) + * @since 1.17.8 + */ +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +@SuppressWarnings({"PMD.AbstractClassWithoutAbstractMethod", "PMD.TooManyMethods"}) +public abstract class RequestITCaseTemplate { + + /** + * Type of Request. + */ + private final Class type; + + /** + * Base URI. + */ + private final URI uri; + + /** + * Make request for a specific path. + * @param path Path. + * @return Request. + */ + protected final Request request(final String path) { + return RequestTestTemplate.request( + this.uri.resolve(path), + this.type + ); + } + + @ParameterizedTest + @ValueSource(ints = { + HttpURLConnection.HTTP_NOT_FOUND, + HttpURLConnection.HTTP_OK, + HttpURLConnection.HTTP_UNAVAILABLE + }) + final void readsReturnStatusCode(final int code) throws IOException { + this.request(String.format("/status/%d", code)) + .fetch() + .as(RestResponse.class) + .assertStatus(code); + } + + @ParameterizedTest + @ValueSource( + strings = { + Request.DELETE, + Request.GET, + Request.POST, + Request.PUT + } + ) + final void readsReturnStatusCode(final String method) throws IOException { + this.request(String.format("/%s", method.toLowerCase(Locale.ENGLISH))) + .method(method) + .fetch() + .as(RestResponse.class) + .assertStatus(HttpURLConnection.HTTP_OK); + } + + @Test + final void sendsCookies() throws IOException { + MatcherAssert.assertThat( + "Must send cookies", + this.request("/cookies") + .header(HttpHeaders.COOKIE, "foo=bar") + .header(HttpHeaders.COOKIE, "baz=foz") + .through(CookieOptimizingWire.class) + .fetch() + .as(JsonResponse.class) + .json() + .readObject() + .getJsonObject("cookies"), + Matchers.allOf( + Matchers.hasEntry( + Matchers.is("foo"), + Matchers.is(Json.createValue("bar")) + ), + Matchers.hasEntry( + Matchers.is("baz"), + Matchers.is(Json.createValue("foz")) + ) + ) + ); + } + + @Test + final void followsLocationHeader() throws IOException { + this.request("/absolute-redirect/5") + .through(AutoRedirectingWire.class, Tv.SIX) + .fetch() + .as(RestResponse.class) + .assertStatus(HttpURLConnection.HTTP_OK); + } + + @Test + final void failsOnTimeout() { + Assertions.assertThrows( + IOException.class, + () -> + this.request("/delay/3") + .timeout(Tv.THOUSAND, Tv.THOUSAND) + .fetch() + ); + } + + @Test + final void readsJsonResponse() throws Exception { + MatcherAssert.assertThat( + "Must parse Json response", + this.request("/json") + .fetch() + .as(JsonResponse.class) + .json() + .readObject(), + Matchers.notNullValue(JsonObject.class) + ); + } + + @Test + @DisabledIf("isJdkRequest") + final void readsDeflatedJsonResponse() throws Exception { + MatcherAssert.assertThat( + "Must undeflate & parse Json response", + this.request("/deflate") + .fetch() + .as(JsonResponse.class) + .json() + .readObject(), + Matchers.hasEntry( + Matchers.is("deflated"), + Matchers.is(JsonValue.TRUE) + ) + ); + } + + @Test + @DisabledIf("isJdkRequest") + final void readsGzippedJsonResponse() throws Exception { + MatcherAssert.assertThat( + "Must unzip & parse Json response", + this.request("/gzip") + .fetch() + .as(JsonResponse.class) + .json() + .readObject(), + Matchers.hasEntry( + Matchers.is("gzipped"), + Matchers.is(JsonValue.TRUE) + ) + ); + } + + @Test + final void handlesBasicAuth() throws Exception { + MatcherAssert.assertThat( + "Must authenticate with userInfo", + this.request("/basic-auth/jeff/secret") + .uri() + .userInfo("jeff:secret") + .back() + .through(BasicAuthWire.class) + .fetch() + .as(JsonResponse.class) + .status(), + Matchers.is( + HttpURLConnection.HTTP_OK + ) + ); + } + + @Test + final void readsXmlResponse() throws Exception { + MatcherAssert.assertThat( + "Must parse XML response", + this.request("/xml") + .fetch() + .as(XmlResponse.class) + .xml(), + Matchers.notNullValue(XML.class) + ); + } + + /** + * Is JdkRequest being tested? + * @return True if so. + */ + @SuppressWarnings("PMD.UnusedPrivateMethod") + private boolean isJdkRequest() { + return JdkRequest.class.equals(this.type); + } +} diff --git a/src/test/java/com/jcabi/http/RequestSecondITCase.java b/src/test/java/com/jcabi/http/RequestSecondITCase.java new file mode 100644 index 00000000..d8e65ca7 --- /dev/null +++ b/src/test/java/com/jcabi/http/RequestSecondITCase.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2011-2017, jcabi.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: 1) Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. 2) Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. 3) Neither the name of the jcabi.com nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jcabi.http; + +import com.jcabi.http.request.ApacheRequest; +import com.jcabi.http.request.JdkRequest; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.TestInstance; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +/** + * Integration test for {@link Request}. + * + * @since 1.17.8 + */ +@SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Testcontainers(disabledWithoutDocker = true) +public final class RequestSecondITCase { + + /** + * Container with HttpBin. + */ + private final GenericContainer container = new GenericContainer<>( + DockerImageName.parse("kennethreitz/httpbin") + ).withExposedPorts(80); + + @BeforeAll + void beforeAll() { + this.container.start(); + } + + @AfterAll + void tearDown() { + this.container.stop(); + } + + /** + * URI of the container. + * @return URI. + */ + private URI uri() { + return URI.create( + String.format( + "http://%s:%d", + this.container.getHost(), + this.container.getFirstMappedPort() + ) + ); + } + + /** + * Test for {@link JdkRequest}. + * @since 1.17.8 + */ + @Nested + final class JdkRequestITCase extends RequestITCaseTemplate { + JdkRequestITCase() { + super(JdkRequest.class, RequestSecondITCase.this.uri()); + } + } + + /** + * Test for {@link ApacheRequest}. + * @since 1.17.8 + */ + @Nested + final class ApacheRequestITCase extends RequestITCaseTemplate { + ApacheRequestITCase() { + super(ApacheRequest.class, RequestSecondITCase.this.uri()); + } + } +} diff --git a/src/test/java/com/jcabi/http/RequestTestTemplate.java b/src/test/java/com/jcabi/http/RequestTestTemplate.java index 68986fef..ba552896 100644 --- a/src/test/java/com/jcabi/http/RequestTestTemplate.java +++ b/src/test/java/com/jcabi/http/RequestTestTemplate.java @@ -34,6 +34,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URI; +import lombok.SneakyThrows; import org.junit.jupiter.params.provider.ValueSource; /** @@ -58,11 +59,9 @@ abstract class RequestTestTemplate { * @param uri URI to start with * @param type Type of the request * @return Request - * @throws Exception If fails */ - static Request request(final URI uri, final Class type) - throws Exception { + @SneakyThrows + static Request request(final URI uri, final Class type) { return type.getDeclaredConstructor(URI.class).newInstance(uri); } - }