From d662b79bacb4447c65c212edbd9047957de850d1 Mon Sep 17 00:00:00 2001 From: jcarranzan Date: Wed, 12 Jun 2024 09:39:17 +0200 Subject: [PATCH] Add backports-3.8.5 coverage for QuarkusCliPomIntegrity and GzipMaxInput --- .../ts/http/advanced/GzipResource.java | 16 +++ .../ts/http/advanced/GzipMaxInputIT.java | 110 ++++++++++++++++++ .../quarkus/cli/QuarkusCliPomIntegrityIT.java | 88 ++++++++++++++ .../src/test/resources/test.properties | 2 + 4 files changed, 216 insertions(+) create mode 100644 http/http-advanced/src/main/java/io/quarkus/ts/http/advanced/GzipResource.java create mode 100644 http/http-advanced/src/test/java/io/quarkus/ts/http/advanced/GzipMaxInputIT.java create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliPomIntegrityIT.java diff --git a/http/http-advanced/src/main/java/io/quarkus/ts/http/advanced/GzipResource.java b/http/http-advanced/src/main/java/io/quarkus/ts/http/advanced/GzipResource.java new file mode 100644 index 0000000000..dae2b36cb2 --- /dev/null +++ b/http/http-advanced/src/main/java/io/quarkus/ts/http/advanced/GzipResource.java @@ -0,0 +1,16 @@ +package io.quarkus.ts.http.advanced; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.resteasy.annotations.GZIP; + +@Path("/gzip") +public class GzipResource { + + @POST + public String gzip(@GZIP byte[] message) { + return "OK"; + } + +} diff --git a/http/http-advanced/src/test/java/io/quarkus/ts/http/advanced/GzipMaxInputIT.java b/http/http-advanced/src/test/java/io/quarkus/ts/http/advanced/GzipMaxInputIT.java new file mode 100644 index 0000000000..82ffda8e6f --- /dev/null +++ b/http/http-advanced/src/test/java/io/quarkus/ts/http/advanced/GzipMaxInputIT.java @@ -0,0 +1,110 @@ +package io.quarkus.ts.http.advanced; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; + +import org.apache.http.HttpHeaders; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.services.QuarkusApplication; +import io.restassured.response.Response; + +@QuarkusScenario +public class GzipMaxInputIT { + final byte[] zero_bytes = new byte[0]; + final String invalid_value = ""; + final byte[] SMALL_PAYLOAD = new byte[512]; + final byte[] LIMIT_PAYLOAD = new byte[100 * 1024 * 1024]; + final byte[] OVER_LIMIT_PAYLOAD = new byte[200 * 1024 * 1024]; + + /** + * + * Tests are checking server response on different size of sent payload + * Limit is configured using quarkus.resteasy.gzip.max-input property + * (According "All configurations options" guide the property 'quarkus.resteasy.gzip.max-input' refers to + * Maximum deflated file bytes size) + * If the limit is exceeded, Resteasy will return a response with status 413("Request Entity Too Large") + */ + @QuarkusApplication(classes = { GzipResource.class }, properties = "gzip.properties") + static RestService app = new RestService(); + + @Test + void sendInvalidContent() { + Response response = sendStringDataToGzipEndpoint(invalid_value); + assertEquals(HttpStatus.SC_BAD_REQUEST, response.statusCode(), + "Invalid data as this void string should result in 400 BAD_REQUEST response"); + } + + @Test + void sendZeroBytesPayload() throws IOException { + byte[] compressedData = generateCompressedData(zero_bytes); + Response response = sendDataToGzipEndpoint(compressedData); + assertEquals(HttpStatus.SC_OK, response.statusCode(), + "The response should be 200 OK because the compression returns 2 bytes"); + } + + @Test + void sendPayloadBelowMaxInputLimit() throws IOException { + byte[] compressedData = generateCompressedData(SMALL_PAYLOAD); + Response response = sendDataToGzipEndpoint(compressedData); + assertEquals(HttpStatus.SC_OK, response.statusCode(), + "The response should be 200 OK because sending just 512 bytes"); + } + + @Tag("https://github.com/quarkusio/quarkus/issues/39636") + @Test + void sendMaximumAllowedPayload() throws IOException { + byte[] compressedData = generateCompressedData(LIMIT_PAYLOAD); + Response response = sendDataToGzipEndpoint(compressedData); + assertEquals(HttpStatus.SC_OK, response.statusCode(), + "The response should be 200 OK because sending just the limit payload configured using " + + "quarkus.resteasy.gzip.max-input=100M. This fails if the suffix format parsing is not " + + "working and RESTEasy falls back to its default which is 10M"); + } + + @Test + void sendMoreThanMaximumAllowedPayload() throws IOException { + byte[] compressedData = generateCompressedData(OVER_LIMIT_PAYLOAD); + Response response = sendDataToGzipEndpoint(compressedData); + assertEquals(HttpStatus.SC_REQUEST_TOO_LONG, response.statusCode(), + "The response should be 413 REQUEST_TOO_LONG when sending larger payload than the limit"); + } + + private Response sendDataToGzipEndpoint(byte[] data) { + return app.given() + .header(HttpHeaders.CONTENT_ENCODING, "gzip") + .body(data) + .when() + .post("/gzip") + .then() + .extract().response(); + } + + private Response sendStringDataToGzipEndpoint(String data) { + return app.given() + .header("Content-Encoding", "gzip") + .body(data) + .when() + .post("/gzip") + .then() + .extract().response(); + } + + public byte[] generateCompressedData(byte[] data) throws IOException { + byte[] result; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) { + gzipOut.write(data); + gzipOut.close(); + result = baos.toByteArray(); + } + return result; + } +} diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliPomIntegrityIT.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliPomIntegrityIT.java new file mode 100644 index 0000000000..2d08b012c4 --- /dev/null +++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliPomIntegrityIT.java @@ -0,0 +1,88 @@ +package io.quarkus.ts.quarkus.cli; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Properties; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.QuarkusCliClient; +import io.quarkus.test.bootstrap.QuarkusCliRestService; +import io.quarkus.test.scenarios.QuarkusScenario; + +@QuarkusScenario +public class QuarkusCliPomIntegrityIT { + /* + * This scenario is related to the backport https://github.com/quarkusio/quarkus/issues/39088 + * + */ + private static final String NEW_EXTENSION = "quarkus-kafka-client"; + private static final String COMMENT = ""; + + private static final String APP_NAME = "my-quarkus-app"; + + private static Path POM_PATH; + + @Inject + static QuarkusCliClient cliClient; + + @BeforeAll + public static void loadPom() { + try (InputStream input = QuarkusCliPomIntegrityIT.class.getClassLoader().getResourceAsStream("test.properties")) { + Properties properties = new Properties(); + if (input == null) { + throw new IOException("Configuration file not found"); + } + properties.load(input); + String rawPath = properties.getProperty("app.pom.path"); + if (rawPath == null) { + throw new RuntimeException("Path configuration is missing"); + } + String pathStr = rawPath + .replace("{className}", QuarkusCliPomIntegrityIT.class.getSimpleName()) + .replace("{appName}", APP_NAME); + POM_PATH = Paths.get(pathStr); + } catch (IOException ex) { + throw new RuntimeException("Failed to load configuration", ex); + } + + } + + @Test + public void shouldKeepCommentInPomAfterAddAndRemoveExtension() throws IOException { + QuarkusCliRestService app = cliClient.createApplication(APP_NAME); + File pomFile = POM_PATH.toFile(); + + try (var fileWriter = new FileWriter(pomFile, true)) { + fileWriter.write(System.lineSeparator()); + fileWriter.write(COMMENT); + } + + // Add extension + app.installExtension(NEW_EXTENSION); + assertPomContainsComment(pomFile, "The comment after add extension still should be there"); + + // Remove extension + app.removeExtension(NEW_EXTENSION); + assertPomContainsComment(pomFile, "The comment after remove extension still should be there"); + } + + private static void assertPomContainsComment(File pom, String errMessage) throws IOException { + List pomContent = Files.readAllLines(pom.toPath()); + // the comment is added to the (new) last line, + // however Quarkus formats the POM file when extension is added / removed + // and removes the new line separator, so we need to look for the comment in the last line; + var lastLine = pomContent.get(pomContent.size() - 1); + Assertions.assertTrue(lastLine.contains(COMMENT), errMessage); + } +} diff --git a/quarkus-cli/src/test/resources/test.properties b/quarkus-cli/src/test/resources/test.properties index b3e5317df0..e005e2deae 100644 --- a/quarkus-cli/src/test/resources/test.properties +++ b/quarkus-cli/src/test/resources/test.properties @@ -2,3 +2,5 @@ ts.global.generated-service.enabled=false #this app is not expected to start, so let's fail right after ts.pomApp.startup.timeout=PT2s ts.pomApp.startup.check-poll-interval=PT1s +app.pom.path=target/{className}/{appName}/pom.xml +