From 8dea0ce83c4c0aac82ad50e8dbcd1f4b8e872616 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 12 May 2024 19:01:26 -0300 Subject: [PATCH 1/9] Updating to Quarkus 3.10.0. --- .github/workflows/test.yml | 2 +- build.gradle | 8 ++++---- gradle.properties | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- src/test/java/com/testainers/BaseResourceTest.java | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d1e61bd..2be47e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'adopt' - java-version: '17' + java-version: '21' - name: Generating self-signed certificate run: | diff --git a/build.gradle b/build.gradle index e45add6..c465b02 100644 --- a/build.gradle +++ b/build.gradle @@ -13,8 +13,8 @@ dependencies { implementation 'io.quarkus:quarkus-micrometer-registry-prometheus' implementation 'io.quarkus:quarkus-smallrye-openapi' implementation "io.quarkus:quarkus-smallrye-health" - implementation 'io.quarkus:quarkus-resteasy-reactive' - implementation 'io.quarkus:quarkus-resteasy-reactive-jackson' + implementation 'io.quarkus:quarkus-rest' + implementation 'io.quarkus:quarkus-rest-jackson' implementation 'io.quarkus:quarkus-container-image-docker' implementation 'io.quarkus:quarkus-info' implementation 'io.quarkus:quarkus-arc' @@ -28,8 +28,8 @@ group 'com.testainers' version '0.0.7' java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } test { diff --git a/gradle.properties b/gradle.properties index 633b193..514c682 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ #Gradle properties quarkusPluginId=io.quarkus -quarkusPluginVersion=3.7.2 +quarkusPluginVersion=3.10.0 quarkusPlatformGroupId=io.quarkus.platform quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.7.2 +quarkusPlatformVersion=3.10.0 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a595206..48c0a02 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/test/java/com/testainers/BaseResourceTest.java b/src/test/java/com/testainers/BaseResourceTest.java index 0b65d87..a0bfe3e 100644 --- a/src/test/java/com/testainers/BaseResourceTest.java +++ b/src/test/java/com/testainers/BaseResourceTest.java @@ -34,6 +34,7 @@ public class BaseResourceTest { .enablePrettyPrinting(true) ) .redirect(redirectConfig().followRedirects(false)); + protected static final Map HEADERS = Map.of("test-header", List.of("test-header-value"), "test-header-2", List.of("test-header-value-2")); From 8cadb02d64a284cd62f063710c8251b49bba6370 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 12 May 2024 19:06:10 -0300 Subject: [PATCH 2/9] Updating main CI script. --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dffb33a..a234cb1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'adopt' - java-version: '17' + java-version: '21' - name: Get Gradle Version run: | @@ -68,7 +68,7 @@ jobs: -Dquarkus.container-image.additional-tags=${{ env.TAGS }} - name: Creating GitHub Tag - uses: mathieudutour/github-tag-action@v6.1 + uses: mathieudutour/github-tag-action@v6.2 with: custom_tag: ${{ env.VERSION }} tag_prefix: '' @@ -86,7 +86,7 @@ jobs: /bin/cp -f README.md build/jacoco-report/. - name: Publishing to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: publish_dir: ./build/jacoco-report github_token: ${{ secrets.GITHUB_TOKEN }} From 15fc4671c66ac9e2d916d37ba634ddcbc53bac86 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 12 May 2024 19:12:03 -0300 Subject: [PATCH 3/9] Updating Dockerfiles. --- src/main/docker/Dockerfile.jvm | 3 +-- src/main/docker/Dockerfile.legacy-jar | 4 ++-- src/main/docker/Dockerfile.native | 2 +- src/main/docker/Dockerfile.native-micro | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm index 91daae1..12619b7 100644 --- a/src/main/docker/Dockerfile.jvm +++ b/src/main/docker/Dockerfile.jvm @@ -77,11 +77,10 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 +FROM registry.access.redhat.com/ubi8/openjdk-21:1.18 ENV LANGUAGE='en_US:en' - # We make four distinct layers so if there are application changes the library layers can be re-used COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ COPY --chown=185 build/quarkus-app/*.jar /deployments/ diff --git a/src/main/docker/Dockerfile.legacy-jar b/src/main/docker/Dockerfile.legacy-jar index 8b3a052..26a3625 100644 --- a/src/main/docker/Dockerfile.legacy-jar +++ b/src/main/docker/Dockerfile.legacy-jar @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 +FROM registry.access.redhat.com/ubi8/openjdk-21:1.18 ENV LANGUAGE='en_US:en' @@ -90,4 +90,4 @@ USER 185 ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" -ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] \ No newline at end of file +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native index efe3bb2..2867d3e 100644 --- a/src/main/docker/Dockerfile.native +++ b/src/main/docker/Dockerfile.native @@ -24,4 +24,4 @@ COPY --chown=1001:root build/*-runner /work/application EXPOSE 8080 USER 1001 -ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/src/main/docker/Dockerfile.native-micro b/src/main/docker/Dockerfile.native-micro index 3b68504..9521a99 100644 --- a/src/main/docker/Dockerfile.native-micro +++ b/src/main/docker/Dockerfile.native-micro @@ -38,4 +38,4 @@ EXPOSE 8443 USER 1001 -ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] From a7ad9a0d4a2bb7781b1bb4d7c1de93a9000aa98b Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 26 May 2024 12:11:08 -0300 Subject: [PATCH 4/9] Migrating to Kotlin. --- build.gradle | 50 -------- build.gradle.kts | 70 +++++++++++ gradle.properties | 4 +- settings.gradle | 11 -- settings.gradle.kts | 13 ++ src/main/docker/Dockerfile.legacy-jar | 2 +- src/main/docker/Dockerfile.native | 2 +- src/main/docker/Dockerfile.native-micro | 2 +- .../java/com/testainers/LengthResource.java | 114 ------------------ .../com/testainers/SimpleHealthCheck.java | 20 --- .../kotlin/com/testainers/LengthResource.kt | 105 ++++++++++++++++ .../com/testainers/SimpleHealthCheck.kt | 14 +++ 12 files changed, 207 insertions(+), 200 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts delete mode 100644 src/main/java/com/testainers/LengthResource.java delete mode 100644 src/main/java/com/testainers/SimpleHealthCheck.java create mode 100644 src/main/kotlin/com/testainers/LengthResource.kt create mode 100644 src/main/kotlin/com/testainers/SimpleHealthCheck.kt diff --git a/build.gradle b/build.gradle deleted file mode 100644 index c465b02..0000000 --- a/build.gradle +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - id 'java' - id 'io.quarkus' -} - -repositories { - mavenCentral() - mavenLocal() -} - -dependencies { - implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") - implementation 'io.quarkus:quarkus-micrometer-registry-prometheus' - implementation 'io.quarkus:quarkus-smallrye-openapi' - implementation "io.quarkus:quarkus-smallrye-health" - implementation 'io.quarkus:quarkus-rest' - implementation 'io.quarkus:quarkus-rest-jackson' - implementation 'io.quarkus:quarkus-container-image-docker' - implementation 'io.quarkus:quarkus-info' - implementation 'io.quarkus:quarkus-arc' - implementation 'io.smallrye.config:smallrye-config-source-file-system' - testImplementation 'io.quarkus:quarkus-junit5' - testImplementation 'io.rest-assured:rest-assured' - testImplementation 'io.quarkus:quarkus-jacoco' -} - -group 'com.testainers' -version '0.0.7' - -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -test { - systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" - - testLogging { - events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" - } -} - -compileJava { - options.encoding = 'UTF-8' - options.compilerArgs << '-parameters' -} - -compileTestJava { - options.encoding = 'UTF-8' -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..9259c57 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + kotlin("jvm") version "1.9.24" + kotlin("plugin.allopen") version "1.9.24" + id("io.quarkus") +} + +repositories { + mavenCentral() + mavenLocal() +} + +val quarkusPlatformGroupId: String by project +val quarkusPlatformArtifactId: String by project +val quarkusPlatformVersion: String by project + +dependencies { + implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("io.smallrye.config:smallrye-config-source-file-system") + implementation("io.quarkus:quarkus-micrometer-registry-prometheus") + implementation("io.quarkus:quarkus-smallrye-openapi") + implementation("io.quarkus:quarkus-smallrye-health") + implementation("io.quarkus:quarkus-rest") + implementation("io.quarkus:quarkus-rest-jackson") + implementation("io.quarkus:quarkus-container-image-docker") + implementation("io.quarkus:quarkus-info") + implementation("io.quarkus:quarkus-kotlin") + implementation("io.quarkus:quarkus-arc") + testImplementation("io.quarkus:quarkus-junit5") + testImplementation("io.rest-assured:rest-assured") + testImplementation("io.rest-assured:kotlin-extensions") + testImplementation("io.quarkus:quarkus-jacoco") +} + +group = "com.testainers" +version = "0.0.8" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.withType { + systemProperty( + "java.util.logging.manager", + "org.jboss.logmanager.LogManager" + ) + + testLogging { + events( + "PASSED", + "SKIPPED", + "FAILED", + "STANDARD_OUT", + "STANDARD_ERROR" + ) + } +} + +allOpen { + annotation("jakarta.ws.rs.Path") + annotation("jakarta.enterprise.context.ApplicationScoped") + annotation("jakarta.persistence.Entity") + annotation("io.quarkus.test.junit.QuarkusTest") +} + +tasks.withType { + kotlinOptions.jvmTarget = JavaVersion.VERSION_21.toString() + kotlinOptions.javaParameters = true +} diff --git a/gradle.properties b/gradle.properties index 514c682..4a11163 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ #Gradle properties quarkusPluginId=io.quarkus -quarkusPluginVersion=3.10.0 +quarkusPluginVersion=3.10.2 quarkusPlatformGroupId=io.quarkus.platform quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.10.0 +quarkusPlatformVersion=3.10.2 \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index c9aec6e..0000000 --- a/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - mavenLocal() - } - plugins { - id "${quarkusPluginId}" version "${quarkusPluginVersion}" - } -} -rootProject.name='httpbucket' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..fa9be2a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,13 @@ +pluginManagement { + val quarkusPluginVersion: String by settings + val quarkusPluginId: String by settings + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + plugins { + id(quarkusPluginId) version quarkusPluginVersion + } +} +rootProject.name="httpbucket" diff --git a/src/main/docker/Dockerfile.legacy-jar b/src/main/docker/Dockerfile.legacy-jar index 26a3625..35f4f5b 100644 --- a/src/main/docker/Dockerfile.legacy-jar +++ b/src/main/docker/Dockerfile.legacy-jar @@ -3,7 +3,7 @@ # # Before building the container image run: # -# ./gradlew build -Dquarkus.package.type=legacy-jar +# ./gradlew build -Dquarkus.package.jar.type=legacy-jar # # Then, build the image with: # diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native index 2867d3e..aec7401 100644 --- a/src/main/docker/Dockerfile.native +++ b/src/main/docker/Dockerfile.native @@ -3,7 +3,7 @@ # # Before building the container image run: # -# ./gradlew build -Dquarkus.package.type=native +# ./gradlew build -Dquarkus.native.enabled=true # # Then, build the image with: # diff --git a/src/main/docker/Dockerfile.native-micro b/src/main/docker/Dockerfile.native-micro index 9521a99..8585bfb 100644 --- a/src/main/docker/Dockerfile.native-micro +++ b/src/main/docker/Dockerfile.native-micro @@ -6,7 +6,7 @@ # # Before building the container image run: # -# ./gradlew build -Dquarkus.package.type=native +# ./gradlew build -Dquarkus.native.enabled=true # # Then, build the image with: # diff --git a/src/main/java/com/testainers/LengthResource.java b/src/main/java/com/testainers/LengthResource.java deleted file mode 100644 index 212e1b0..0000000 --- a/src/main/java/com/testainers/LengthResource.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.testainers; - -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.openapi.annotations.media.Content; -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; -import org.jboss.resteasy.reactive.RestHeader; - -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * @author Eduardo Folly - */ -@Path("/length/{size}") -@APIResponses({ - @APIResponse(responseCode = "200", content = { - @Content(mediaType = MediaType.TEXT_PLAIN), - @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM) - }), - @APIResponse(responseCode = "500", description = "Invalid size: X.") -}) -public class LengthResource { - - @GET - public Response get(@RestHeader(HttpHeaders.ACCEPT) String accept, - @Parameter(description = "Size must be " + - "between 1 and 2048.", - schema = @Schema(minimum = "1", - maximum = "2048", - defaultValue = "10" - )) int size) { - - return internal(accept, size); - } - - @POST - public Response post(@RestHeader(HttpHeaders.ACCEPT) String accept, - @Parameter(description = "Size must be " + - "between 1 and 2048.", - schema = @Schema(minimum = "1", - maximum = "2048", - defaultValue = "10" - )) int size) { - - return internal(accept, size); - } - - @PUT - public Response put(@RestHeader(HttpHeaders.ACCEPT) String accept, - @Parameter(description = "Size must be " + - "between 1 and 2048.", - schema = @Schema(minimum = "1", - maximum = "2048", - defaultValue = "10" - )) int size) { - - return internal(accept, size); - } - - @PATCH - public Response patch(@RestHeader(HttpHeaders.ACCEPT) String accept, - @Parameter(description = "Size must be " + - "between 1 and 2048.", - schema = @Schema(minimum = "1", - maximum = "2048", - defaultValue = "10" - )) int size) { - - return internal(accept, size); - } - - @DELETE - public Response delete(@RestHeader(HttpHeaders.ACCEPT) String accept, - @Parameter(description = "Size must be " + - "between 1 and 2048.", - schema = @Schema(minimum = "1", - maximum = "2048", - defaultValue = "10" - )) int size) { - - return internal(accept, size); - } - - private Response internal(String accept, int size) { - if (size < 1 || size > 2048) { - return Response - .status(500) - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid size: %d", size)) - .build(); - } - - if (MediaType.APPLICATION_OCTET_STREAM.equals(accept)) { - return Response - .ok(new byte[size], MediaType.APPLICATION_OCTET_STREAM_TYPE) - .build(); - } else { - return Response - .ok(IntStream.range(0, size) - .boxed() - .map(integer -> "0") - .collect(Collectors.joining()), - MediaType.TEXT_PLAIN_TYPE) - .build(); - } - } - -} diff --git a/src/main/java/com/testainers/SimpleHealthCheck.java b/src/main/java/com/testainers/SimpleHealthCheck.java deleted file mode 100644 index 61bff31..0000000 --- a/src/main/java/com/testainers/SimpleHealthCheck.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.testainers; - -import jakarta.enterprise.context.ApplicationScoped; -import org.eclipse.microprofile.health.HealthCheck; -import org.eclipse.microprofile.health.HealthCheckResponse; -import org.eclipse.microprofile.health.Liveness; - -/** - * @author Eduardo Folly - */ -@Liveness -@ApplicationScoped -public class SimpleHealthCheck implements HealthCheck { - - @Override - public HealthCheckResponse call() { - return HealthCheckResponse.up("httpbucket"); - } - -} diff --git a/src/main/kotlin/com/testainers/LengthResource.kt b/src/main/kotlin/com/testainers/LengthResource.kt new file mode 100644 index 0000000..19ef588 --- /dev/null +++ b/src/main/kotlin/com/testainers/LengthResource.kt @@ -0,0 +1,105 @@ +package com.testainers + +import jakarta.ws.rs.* +import jakarta.ws.rs.core.* +import org.eclipse.microprofile.openapi.annotations.media.Content +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses +import org.jboss.resteasy.reactive.RestHeader + +/** + * @author Eduardo Folly + */ +@Path("/length/{size}") +@APIResponses( + APIResponse( + responseCode = "200", + content = [Content(mediaType = MediaType.TEXT_PLAIN), Content(mediaType = MediaType.APPLICATION_OCTET_STREAM)] + ), APIResponse(responseCode = "500", description = "Invalid size: X.") +) +class LengthResource { + @GET + fun get( + @RestHeader(HttpHeaders.ACCEPT) accept: String, + @Parameter( + description = "Size must be between 1 and 2048.", + schema = Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10" + ) + ) size: Int + ): Response = internal(accept, size) + + @POST + fun post( + @RestHeader(HttpHeaders.ACCEPT) accept: String, + @Parameter( + description = "Size must be between 1 and 2048.", + schema = Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10" + ) + ) size: Int + ): Response = internal(accept, size) + + @PUT + fun put( + @RestHeader(HttpHeaders.ACCEPT) accept: String, + @Parameter( + description = "Size must be between 1 and 2048.", + schema = Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10" + ) + ) size: Int + ): Response = internal(accept, size) + + @PATCH + fun patch( + @RestHeader(HttpHeaders.ACCEPT) accept: String, + @Parameter( + description = "Size must be between 1 and 2048.", + schema = Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10" + ) + ) size: Int + ): Response = internal(accept, size) + + @DELETE + fun delete( + @RestHeader(HttpHeaders.ACCEPT) accept: String, + @Parameter( + description = "Size must be between 1 and 2048.", + schema = Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10" + ) + ) size: Int + ): Response = internal(accept, size) + + private fun internal(accept: String, size: Int): Response { + if (size < 1 || size > 2048) { + return Response + .status(500) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity(String.format("Invalid size: %d", size)) + .build() + } + + return if (MediaType.APPLICATION_OCTET_STREAM == accept) { + Response + .ok(ByteArray(size), MediaType.APPLICATION_OCTET_STREAM_TYPE) + .build() + } else { + Response.ok("0".repeat(size), MediaType.TEXT_PLAIN_TYPE).build() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/SimpleHealthCheck.kt b/src/main/kotlin/com/testainers/SimpleHealthCheck.kt new file mode 100644 index 0000000..9879c4e --- /dev/null +++ b/src/main/kotlin/com/testainers/SimpleHealthCheck.kt @@ -0,0 +1,14 @@ +package com.testainers + +import jakarta.enterprise.context.ApplicationScoped +import org.eclipse.microprofile.health.* + +/** + * @author Eduardo Folly + */ +@Liveness +@ApplicationScoped +class SimpleHealthCheck : HealthCheck { + override fun call(): HealthCheckResponse = + HealthCheckResponse.up("httpbucket") +} \ No newline at end of file From abef8455fb185d5dc2e88927ffcd66529476d72b Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 26 May 2024 19:24:10 -0300 Subject: [PATCH 5/9] Migrating to Kotlin. --- .../com/testainers/BasicAuthResource.java | 111 ----------------- .../java/com/testainers/DelayResource.java | 103 ---------------- .../java/com/testainers/MethodsResource.java | 55 --------- .../java/com/testainers/RedirectResource.java | 116 ------------------ .../java/com/testainers/ResponseBody.java | 48 -------- .../java/com/testainers/StatusResource.java | 99 --------------- .../com/testainers/BasicAuthResource.kt | 98 +++++++++++++++ .../kotlin/com/testainers/DelayResource.kt | 84 +++++++++++++ .../kotlin/com/testainers/LengthResource.kt | 22 ++-- .../kotlin/com/testainers/MethodsResource.kt | 35 ++++++ .../kotlin/com/testainers/RedirectResource.kt | 115 +++++++++++++++++ .../kotlin/com/testainers/ResponseBody.kt | 37 ++++++ .../kotlin/com/testainers/StatusResource.kt | 83 +++++++++++++ .../com/testainers/RedirectResourceTest.java | 2 +- 14 files changed, 464 insertions(+), 544 deletions(-) delete mode 100644 src/main/java/com/testainers/BasicAuthResource.java delete mode 100644 src/main/java/com/testainers/DelayResource.java delete mode 100644 src/main/java/com/testainers/MethodsResource.java delete mode 100644 src/main/java/com/testainers/RedirectResource.java delete mode 100644 src/main/java/com/testainers/ResponseBody.java delete mode 100644 src/main/java/com/testainers/StatusResource.java create mode 100644 src/main/kotlin/com/testainers/BasicAuthResource.kt create mode 100644 src/main/kotlin/com/testainers/DelayResource.kt create mode 100644 src/main/kotlin/com/testainers/MethodsResource.kt create mode 100644 src/main/kotlin/com/testainers/RedirectResource.kt create mode 100644 src/main/kotlin/com/testainers/ResponseBody.kt create mode 100644 src/main/kotlin/com/testainers/StatusResource.kt diff --git a/src/main/java/com/testainers/BasicAuthResource.java b/src/main/java/com/testainers/BasicAuthResource.java deleted file mode 100644 index 5bf088b..0000000 --- a/src/main/java/com/testainers/BasicAuthResource.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.testainers; - -import io.quarkus.security.Authenticated; -import io.vertx.core.http.HttpServerRequest; -import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriInfo; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; -import org.jboss.resteasy.reactive.RestHeader; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Eduardo Folly - */ -@Authenticated -@Path("/basic-auth/{user}/{pass}") -@APIResponses({ - @APIResponse(responseCode = "200"), - @APIResponse(responseCode = "401"), - @APIResponse(responseCode = "403"), -}) -@Produces(MediaType.APPLICATION_JSON) -public class BasicAuthResource { - - @Inject - HttpServerRequest request; - - @Inject - UriInfo uriInfo; - - @GET - public Response get(@RestHeader(HttpHeaders.AUTHORIZATION) String auth, - String user, String pass) { - - return getResponse(auth, user, pass, null); - } - - @POST - public Response post(@RestHeader(HttpHeaders.AUTHORIZATION) String auth, - String user, String pass, Object body) { - - return getResponse(auth, user, pass, body); - } - - @PUT - public Response put(@RestHeader(HttpHeaders.AUTHORIZATION) String auth, - String user, String pass, Object body) { - - return getResponse(auth, user, pass, body); - } - - @PATCH - public Response patch(@RestHeader(HttpHeaders.AUTHORIZATION) String auth, - String user, String pass, Object body) { - - return getResponse(auth, user, pass, body); - } - - @DELETE - public Response delete(@RestHeader(HttpHeaders.AUTHORIZATION) String auth, - String user, String pass, Object body) { - - return getResponse(auth, user, pass, body); - } - - private Response getResponse(String auth, String user, String pass, - Object body) { - ResponseBody responseBody = new ResponseBody(request, uriInfo, body); - int code = 403; - - Map bodyMap = new HashMap<>(); - bodyMap.put("auth", false); - bodyMap.put("user", user); - bodyMap.put("pass", pass); - bodyMap.put("message", "Forbidden."); - bodyMap.put("body", body); - - if (auth == null || auth.isBlank()) { - code = 401; - bodyMap.put("message", "Authorization header not present."); - } else { - String encoded = Base64 - .getEncoder() - .encodeToString((user + ":" + pass) - .getBytes(StandardCharsets.UTF_8)); - - boolean ok = auth.equals("Basic " + encoded); - - bodyMap.put("auth", ok); - - if (ok) { - code = 200; - bodyMap.put("message", "Success."); - } - - } - - responseBody.body = bodyMap; - - return Response.status(code).entity(responseBody).build(); - } - -} diff --git a/src/main/java/com/testainers/DelayResource.java b/src/main/java/com/testainers/DelayResource.java deleted file mode 100644 index bb57885..0000000 --- a/src/main/java/com/testainers/DelayResource.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.testainers; - -import io.vertx.core.http.HttpServerRequest; -import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriInfo; -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; - -/** - * @author Eduardo Folly - */ -@Path("/delay/{delay}") -@APIResponses({ - @APIResponse(responseCode = "200"), - @APIResponse(responseCode = "400"), - @APIResponse(responseCode = "500"), -}) -@Produces(MediaType.APPLICATION_JSON) -public class DelayResource { - - @Inject - HttpServerRequest request; - - @Inject - UriInfo uriInfo; - - @GET - public Response get( - @Parameter(description = "Delay must be between 0 and 10 seconds.", - schema = @Schema(minimum = "0", maximum = "10", - defaultValue = "10")) int delay) { - - return internal(delay, null); - } - - @POST - public Response post( - @Parameter(description = "Delay must be between 0 and 10 seconds.", - schema = @Schema(minimum = "0", maximum = "10", - defaultValue = "10")) - int delay, Object body) { - - return internal(delay, body); - } - - @PUT - public Response put( - @Parameter(description = "Delay must be between 0 and 10 seconds.", - schema = @Schema(minimum = "0", maximum = "10", - defaultValue = "10")) - int delay, Object body) { - - return internal(delay, body); - } - - @PATCH - public Response patch( - @Parameter(description = "Delay must be between 0 and 10 seconds.", - schema = @Schema(minimum = "0", maximum = "10", - defaultValue = "10")) - int delay, Object body) { - - return internal(delay, body); - } - - @DELETE - public Response delete( - @Parameter(description = "Delay must be between 0 and 10 seconds.", - schema = @Schema(minimum = "0", maximum = "10", - defaultValue = "10")) - int delay, Object body) { - - return internal(delay, body); - } - - private Response internal(int delay, Object body) { - ResponseBody responseBody = new ResponseBody(request, uriInfo, body); - int code = 200; - - if (delay < 0 || delay > 10) { - code = 400; - responseBody.body = String.format("Invalid delay: %d", delay); - } else { - try { - Thread.sleep(delay * 1000L); - responseBody.body = - String.format("Slept for %d seconds.", delay); - } catch (InterruptedException e) { - code = 500; - responseBody.body = - String.format("Interrupted: %s", e.getMessage()); - } - } - - return Response.status(code).entity(responseBody).build(); - } - -} diff --git a/src/main/java/com/testainers/MethodsResource.java b/src/main/java/com/testainers/MethodsResource.java deleted file mode 100644 index 142219a..0000000 --- a/src/main/java/com/testainers/MethodsResource.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.testainers; - -import io.vertx.core.http.HttpServerRequest; -import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.*; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; - -/** - * @author Eduardo Folly - */ -@Path("/methods") -@APIResponses({@APIResponse(responseCode = "200")}) -@Produces(MediaType.APPLICATION_JSON) -public class MethodsResource { - - @Inject - HttpServerRequest request; - - @Inject - UriInfo uriInfo; - - @GET - public ResponseBody get() { - return new ResponseBody(request, uriInfo, null); - } - - @HEAD - public ResponseBody head() { - return new ResponseBody(request, uriInfo, null); - } - - @POST - public ResponseBody post(Object body) { - return new ResponseBody(request, uriInfo, body); - } - - @PUT - public ResponseBody put(Object body) { - return new ResponseBody(request, uriInfo, body); - } - - @PATCH - public ResponseBody patch(Object body) { - return new ResponseBody(request, uriInfo, body); - } - - @DELETE - public ResponseBody delete(Object body) { - return new ResponseBody(request, uriInfo, body); - } - -} - diff --git a/src/main/java/com/testainers/RedirectResource.java b/src/main/java/com/testainers/RedirectResource.java deleted file mode 100644 index 0926a31..0000000 --- a/src/main/java/com/testainers/RedirectResource.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.testainers; - -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; - -import java.net.URI; - -/** - * @author Eduardo Folly - */ -@Path("/redirect") -@APIResponses({ - @APIResponse(responseCode = "30X", description = "Redirected"), - @APIResponse(responseCode = "500", - description = "'Invalid URL' or " + - "'Invalid URL Scheme' or " + - "'Invalid status code: 30X'") -}) -public class RedirectResource { - - @GET - public Response get( - @Parameter(description = "URL to redirect.", required = true) - @QueryParam("url") String url, - @Parameter(description = "Response status code.", - schema = @Schema(minimum = "300", maximum = "399")) - @QueryParam("code") @DefaultValue("302") Integer code) { - - return internal(url, code); - } - - @POST - public Response post( - @Parameter(description = "URL to redirect.", required = true) - @QueryParam("url") String url, - @Parameter(description = "Response status code.", - schema = @Schema(minimum = "300", maximum = "399")) - @QueryParam("code") @DefaultValue("302") Integer code) { - - return internal(url, code); - } - - @PUT - public Response put( - @Parameter(description = "URL to redirect.", required = true) - @QueryParam("url") String url, - @Parameter(description = "Response status code.", - schema = @Schema(minimum = "300", maximum = "399")) - @QueryParam("code") @DefaultValue("302") Integer code) { - - return internal(url, code); - } - - @PATCH - public Response patch( - @Parameter(description = "URL to redirect.", required = true) - @QueryParam("url") String url, - @Parameter(description = "Response status code.", - schema = @Schema(minimum = "300", maximum = "399")) - @QueryParam("code") @DefaultValue("302") Integer code) { - - return internal(url, code); - } - - @DELETE - public Response delete( - @Parameter(description = "URL to redirect.", required = true) - @QueryParam("url") String url, - @Parameter(description = "Response status code.", - schema = @Schema(minimum = "300", maximum = "399")) - @QueryParam("code") @DefaultValue("302") Integer code) { - - return internal(url, code); - } - - private Response internal(String url, Integer code) { - URI uri; - - try { - uri = URI.create(url); - } catch (Exception e) { - return Response - .status(500) - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid URL: %s", url)) - .build(); - } - - if (uri.getScheme() == null) { - return Response - .status(500) - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid URL Scheme: %s", url)) - .build(); - } - - if (code < 300 || code > 399) { - return Response - .status(500) - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid status code: %d", code)) - .build(); - } - - return Response.status(code) - .header(HttpHeaders.LOCATION, uri.toASCIIString()) - .build(); - } - -} diff --git a/src/main/java/com/testainers/ResponseBody.java b/src/main/java/com/testainers/ResponseBody.java deleted file mode 100644 index 879b53b..0000000 --- a/src/main/java/com/testainers/ResponseBody.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.testainers; - -import io.quarkus.runtime.annotations.RegisterForReflection; -import io.vertx.core.http.HttpServerRequest; -import jakarta.ws.rs.core.MultivaluedHashMap; -import jakarta.ws.rs.core.MultivaluedMap; -import jakarta.ws.rs.core.UriInfo; - -import java.net.URI; -import java.util.Map; - -/** - * @author Eduardo Folly - */ -@RegisterForReflection -public class ResponseBody { - - public URI uri; - public String method; - public String remoteAddress; - public String remoteHost; - public MultivaluedMap headers; - public MultivaluedMap pathParameters; - public MultivaluedMap queryParameters; - - public Object body; - - public ResponseBody(HttpServerRequest request, UriInfo uriInfo, - Object body) { - - this.method = request.method().name(); - this.remoteAddress = request.remoteAddress().toString(); - this.remoteHost = request.remoteAddress().host(); - - headers = new MultivaluedHashMap<>(); - - for (Map.Entry entry : request.headers()) { - headers.add(entry.getKey(), entry.getValue()); - } - - this.uri = uriInfo.getAbsolutePath(); - this.pathParameters = uriInfo.getPathParameters(); - this.queryParameters = uriInfo.getQueryParameters(); - - this.body = body; - } - -} diff --git a/src/main/java/com/testainers/StatusResource.java b/src/main/java/com/testainers/StatusResource.java deleted file mode 100644 index 4d38551..0000000 --- a/src/main/java/com/testainers/StatusResource.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.testainers; - -import io.vertx.core.http.HttpServerRequest; -import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriInfo; -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; - -/** - * @author Eduardo Folly - */ -@Path("/status/{code}") -@Produces(MediaType.APPLICATION_JSON) -public class StatusResource { - - @Inject - HttpServerRequest request; - - @Inject - UriInfo uriInfo; - - @GET - public Response get( - @Parameter(description = "Code must be between 200 and 599. " + - "Informational responses (1XX) " + - "are not supported.", - schema = @Schema(minimum = "200", maximum = "599") - ) Integer code) { - - return internal(code, null); - } - - @POST - public Response post( - @Parameter(description = "Code must be between 200 and 599. " + - "Informational responses (1XX) " + - "are not supported.", - schema = @Schema(minimum = "200", maximum = "599") - ) Integer code, Object body) { - - return internal(code, body); - } - - @PUT - public Response put( - @Parameter(description = "Code must be between 200 and 599. " + - "Informational responses (1XX) " + - "are not supported.", - schema = @Schema(minimum = "200", maximum = "599") - ) Integer code, Object body) { - - return internal(code, body); - } - - @PATCH - public Response patch( - @Parameter(description = "Code must be between 200 and 599. " + - "Informational responses (1XX) " + - "are not supported.", - schema = @Schema(minimum = "200", maximum = "599") - ) Integer code, Object body) { - - return internal(code, body); - } - - @DELETE - public Response delete( - @Parameter(description = "Code must be between 200 and 599. " + - "Informational responses (1XX) " + - "are not supported.", - schema = @Schema(minimum = "200", maximum = "599") - ) Integer code, Object body) { - - return internal(code, body); - } - - private Response internal(int code, Object body) { - ResponseBody responseBody = new ResponseBody(request, uriInfo, body); - - if (code < 200 || code > 599) { - String message; - - if (code > 99 && code < 200) { - message = "Informational responses are not supported: %d"; - } else { - message = "Unknown status code: %d"; - } - - responseBody.body = String.format(message, code); - code = 500; - } - - return Response.status(code).entity(responseBody).build(); - } - -} diff --git a/src/main/kotlin/com/testainers/BasicAuthResource.kt b/src/main/kotlin/com/testainers/BasicAuthResource.kt new file mode 100644 index 0000000..0af5028 --- /dev/null +++ b/src/main/kotlin/com/testainers/BasicAuthResource.kt @@ -0,0 +1,98 @@ +package com.testainers + +import io.quarkus.security.Authenticated +import io.vertx.core.http.HttpServerRequest +import jakarta.inject.Inject +import jakarta.ws.rs.* +import jakarta.ws.rs.core.* +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses +import org.jboss.resteasy.reactive.RestHeader +import java.nio.charset.StandardCharsets +import java.util.* + +/** + * @author Eduardo Folly + */ +@Authenticated +@Path("/basic-auth/{user}/{pass}") +@APIResponses( + APIResponse(responseCode = "200"), + APIResponse(responseCode = "401"), + APIResponse(responseCode = "403") +) +@Produces(MediaType.APPLICATION_JSON) +class BasicAuthResource(val request: HttpServerRequest, val uriInfo: UriInfo) { + + @GET + fun get( + @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, + user: String, pass: String + ): Response = getResponse(auth, user, pass, null) + + @POST + fun post( + @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, + user: String, pass: String, body: Any? + ): Response = getResponse(auth, user, pass, body) + + @PUT + fun put( + @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, + user: String, pass: String, body: Any? + ): Response = getResponse(auth, user, pass, body) + + @PATCH + fun patch( + @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, + user: String, pass: String, body: Any? + ): Response = getResponse(auth, user, pass, body) + + @DELETE + fun delete( + @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, + user: String, pass: String, body: Any? + ): Response = getResponse(auth, user, pass, body) + + private fun getResponse( + auth: String?, + user: String, + pass: String, + body: Any?, + ): Response { + val responseBody = ResponseBody(request, uriInfo, body) + var code = 403 + + val bodyMap: MutableMap = HashMap() + bodyMap["auth"] = false + bodyMap["user"] = user + bodyMap["pass"] = pass + bodyMap["message"] = "Forbidden." + bodyMap["body"] = body + + if (auth.isNullOrBlank()) { + code = 401 + bodyMap["message"] = "Authorization header not present." + } else { + val encoded = Base64 + .getEncoder() + .encodeToString( + "$user:$pass" + .toByteArray(StandardCharsets.UTF_8) + ) + + val ok = auth == "Basic $encoded" + + bodyMap["auth"] = ok + + if (ok) { + code = 200 + bodyMap["message"] = "Success." + } + } + + responseBody.body = bodyMap + + return Response.status(code).entity(responseBody).build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/DelayResource.kt b/src/main/kotlin/com/testainers/DelayResource.kt new file mode 100644 index 0000000..81c653f --- /dev/null +++ b/src/main/kotlin/com/testainers/DelayResource.kt @@ -0,0 +1,84 @@ +package com.testainers + +import io.vertx.core.http.HttpServerRequest +import jakarta.inject.Inject +import jakarta.ws.rs.* +import jakarta.ws.rs.core.* +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses + +/** + * @author Eduardo Folly + */ +@Path("/delay/{delay}") +@APIResponses( + APIResponse(responseCode = "200"), + APIResponse(responseCode = "400"), + APIResponse(responseCode = "500") +) +@Produces(MediaType.APPLICATION_JSON) +class DelayResource(val request: HttpServerRequest, val uriInfo: UriInfo) { + + @GET + fun get( + @Parameter( + description = "Delay must be between 0 and 10 seconds.", + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") + ) delay: Int + ): Response = internal(delay, null) + + @POST + fun post( + @Parameter( + description = "Delay must be between 0 and 10 seconds.", + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") + ) delay: Int, body: Any? + ): Response = internal(delay, body) + + @PUT + fun put( + @Parameter( + description = "Delay must be between 0 and 10 seconds.", + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") + ) delay: Int, body: Any? + ): Response = internal(delay, body) + + @PATCH + fun patch( + @Parameter( + description = "Delay must be between 0 and 10 seconds.", + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") + ) delay: Int, body: Any? + ): Response = internal(delay, body) + + @DELETE + fun delete( + @Parameter( + description = "Delay must be between 0 and 10 seconds.", + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") + ) delay: Int, body: Any? + ): Response = internal(delay, body) + + private fun internal(delay: Int, body: Any?): Response { + val responseBody = ResponseBody(request, uriInfo, body) + var code = 200 + + if (delay < 0 || delay > 10) { + code = 400 + responseBody.body = String.format("Invalid delay: %d", delay) + } else { + try { + Thread.sleep(delay * 1000L) + responseBody.body = + String.format("Slept for %d seconds.", delay) + } catch (e: InterruptedException) { + code = 500 + responseBody.body = String.format("Interrupted: %s", e.message) + } + } + + return Response.status(code).entity(responseBody).build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/LengthResource.kt b/src/main/kotlin/com/testainers/LengthResource.kt index 19ef588..d0e808c 100644 --- a/src/main/kotlin/com/testainers/LengthResource.kt +++ b/src/main/kotlin/com/testainers/LengthResource.kt @@ -85,21 +85,21 @@ class LengthResource { ) size: Int ): Response = internal(accept, size) - private fun internal(accept: String, size: Int): Response { + private fun internal(accept: String, size: Int): Response = if (size < 1 || size > 2048) { - return Response - .status(500) + Response.status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) .entity(String.format("Invalid size: %d", size)) .build() - } - - return if (MediaType.APPLICATION_OCTET_STREAM == accept) { - Response - .ok(ByteArray(size), MediaType.APPLICATION_OCTET_STREAM_TYPE) - .build() } else { - Response.ok("0".repeat(size), MediaType.TEXT_PLAIN_TYPE).build() + if (MediaType.APPLICATION_OCTET_STREAM == accept) { + Response.ok( + ByteArray(size), + MediaType.APPLICATION_OCTET_STREAM_TYPE + ).build() + } else { + Response.ok("0".repeat(size), MediaType.TEXT_PLAIN_TYPE).build() + } } - } + } \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/MethodsResource.kt b/src/main/kotlin/com/testainers/MethodsResource.kt new file mode 100644 index 0000000..a99c5aa --- /dev/null +++ b/src/main/kotlin/com/testainers/MethodsResource.kt @@ -0,0 +1,35 @@ +package com.testainers + +import io.vertx.core.http.HttpServerRequest +import jakarta.ws.rs.* +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.core.UriInfo +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses + +/** + * @author Eduardo Folly + */ +@Path("/methods") +@APIResponses(APIResponse(responseCode = "200")) +@Produces(MediaType.APPLICATION_JSON) +class MethodsResource(val request: HttpServerRequest, val uriInfo: UriInfo) { + + @GET + fun get(): ResponseBody = ResponseBody(request, uriInfo, null) + + @HEAD + fun head(): ResponseBody = ResponseBody(request, uriInfo, null) + + @POST + fun post(body: Any?): ResponseBody = ResponseBody(request, uriInfo, body) + + @PUT + fun put(body: Any?): ResponseBody = ResponseBody(request, uriInfo, body) + + @PATCH + fun patch(body: Any?): ResponseBody = ResponseBody(request, uriInfo, body) + + @DELETE + fun delete(body: Any?): ResponseBody = ResponseBody(request, uriInfo, body) +} \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/RedirectResource.kt b/src/main/kotlin/com/testainers/RedirectResource.kt new file mode 100644 index 0000000..0d6349f --- /dev/null +++ b/src/main/kotlin/com/testainers/RedirectResource.kt @@ -0,0 +1,115 @@ +package com.testainers + +import jakarta.ws.rs.* +import jakarta.ws.rs.core.* +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses +import java.net.URI + +/** + * @author Eduardo Folly + */ +@Path("/redirect") +@APIResponses( + APIResponse(responseCode = "30X", description = "Redirected"), APIResponse( + responseCode = "500", + description = "'Invalid URL' or 'Invalid URL Scheme' or 'Invalid status code: 30X'" + ) +) +class RedirectResource { + @GET + fun get( + @Parameter( + description = "URL to redirect.", + required = true + ) @QueryParam("url") @DefaultValue("") url: String, + @Parameter( + description = "Response status code.", + schema = Schema(minimum = "300", maximum = "399") + ) @QueryParam("code") @DefaultValue("302") code: Int + ): Response = internal(url, code) + + @POST + fun post( + @Parameter( + description = "URL to redirect.", + required = true + ) @QueryParam("url") @DefaultValue("") url: String, + @Parameter( + description = "Response status code.", + schema = Schema(minimum = "300", maximum = "399") + ) @QueryParam("code") @DefaultValue("302") code: Int + ): Response = internal(url, code) + + @PUT + fun put( + @Parameter( + description = "URL to redirect.", + required = true + ) @QueryParam("url") @DefaultValue("") url: String, + @Parameter( + description = "Response status code.", + schema = Schema(minimum = "300", maximum = "399") + ) @QueryParam("code") @DefaultValue("302") code: Int + ): Response = internal(url, code) + + @PATCH + fun patch( + @Parameter( + description = "URL to redirect.", + required = true + ) @QueryParam("url") @DefaultValue("") url: String, + @Parameter( + description = "Response status code.", + schema = Schema(minimum = "300", maximum = "399") + ) @QueryParam("code") @DefaultValue("302") code: Int + ): Response = internal(url, code) + + @DELETE + fun delete( + @Parameter( + description = "URL to redirect.", + required = true + ) @QueryParam("url") @DefaultValue("") url: String, + @Parameter( + description = "Response status code.", + schema = Schema(minimum = "300", maximum = "399") + ) @QueryParam("code") @DefaultValue("302") code: Int + ): Response = internal(url, code) + + private fun internal(url: String, code: Int): Response { + val uri: URI + + try { + uri = URI.create(url) + } catch (e: Exception) { + return Response + .status(500) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity(String.format("Invalid URL: %s", url)) + .build() + } + + if (uri.scheme == null) { + return Response + .status(500) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity(String.format("Invalid URL Scheme: %s", url)) + .build() + } + + if (code < 300 || code > 399) { + return Response + .status(500) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity(String.format("Invalid status code: %d", code)) + .build() + } + + return Response.status(code) + .header(HttpHeaders.LOCATION, uri.toASCIIString()) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/ResponseBody.kt b/src/main/kotlin/com/testainers/ResponseBody.kt new file mode 100644 index 0000000..62f35a2 --- /dev/null +++ b/src/main/kotlin/com/testainers/ResponseBody.kt @@ -0,0 +1,37 @@ +package com.testainers + +import io.quarkus.runtime.annotations.RegisterForReflection +import io.vertx.core.http.HttpServerRequest +import jakarta.ws.rs.core.* +import java.net.URI + +/** + * @author Eduardo Folly + */ +@RegisterForReflection +class ResponseBody( + request: HttpServerRequest, uriInfo: UriInfo, + body: Any? +) { + var uri: URI + var method: String = request.method().name() + var remoteAddress: String = request.remoteAddress().toString() + var remoteHost: String = request.remoteAddress().host() + var headers: MultivaluedMap = MultivaluedHashMap() + var pathParameters: MultivaluedMap + var queryParameters: MultivaluedMap + + var body: Any? + + init { + for ((key, value) in request.headers()) { + headers.add(key, value) + } + + this.uri = uriInfo.absolutePath + this.pathParameters = uriInfo.pathParameters + this.queryParameters = uriInfo.queryParameters + + this.body = body + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/testainers/StatusResource.kt b/src/main/kotlin/com/testainers/StatusResource.kt new file mode 100644 index 0000000..2d71225 --- /dev/null +++ b/src/main/kotlin/com/testainers/StatusResource.kt @@ -0,0 +1,83 @@ +package com.testainers + +import io.vertx.core.http.HttpServerRequest +import jakarta.ws.rs.* +import jakarta.ws.rs.core.* +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter + +/** + * @author Eduardo Folly + */ +@Path("/status/{code}") +@Produces(MediaType.APPLICATION_JSON) +class StatusResource(val request: HttpServerRequest, val uriInfo: UriInfo) { + @GET + fun get( + @Parameter( + description = "Code must be between 200 and 599. " + + "Informational responses (1XX) are not supported.", + schema = Schema(minimum = "200", maximum = "599") + ) code: Int + ): Response { + return internal(code, null) + } + + @POST + fun post( + @Parameter( + description = "Code must be between 200 and 599. " + + "Informational responses (1XX) are not supported.", + schema = Schema(minimum = "200", maximum = "599") + ) code: Int, body: Any? + ): Response { + return internal(code, body) + } + + @PUT + fun put( + @Parameter( + description = "Code must be between 200 and 599. " + + "Informational responses (1XX) are not supported.", + schema = Schema(minimum = "200", maximum = "599") + ) code: Int, body: Any? + ): Response { + return internal(code, body) + } + + @PATCH + fun patch( + @Parameter( + description = "Code must be between 200 and 599. " + + "Informational responses (1XX) are not supported.", + schema = Schema(minimum = "200", maximum = "599") + ) code: Int, body: Any? + ): Response = internal(code, body) + + @DELETE + fun delete( + @Parameter( + description = "Code must be between 200 and 599. " + + "Informational responses (1XX) are not supported.", + schema = Schema(minimum = "200", maximum = "599") + ) code: Int, body: Any? + ): Response = internal(code, body) + + private fun internal(code: Int, body: Any?): Response { + var status = code + val responseBody = ResponseBody(request, uriInfo, body) + + if (status < 200 || status > 599) { + val message = if (status in 100..199) { + "Informational responses are not supported: %d" + } else { + "Unknown status code: %d" + } + + responseBody.body = String.format(message, status) + status = 500 + } + + return Response.status(status).entity(responseBody).build() + } +} \ No newline at end of file diff --git a/src/test/java/com/testainers/RedirectResourceTest.java b/src/test/java/com/testainers/RedirectResourceTest.java index 8add5db..b91832b 100644 --- a/src/test/java/com/testainers/RedirectResourceTest.java +++ b/src/test/java/com/testainers/RedirectResourceTest.java @@ -100,7 +100,7 @@ public void redirectNoUrl() { .then() .statusCode(500) .contentType(ContentType.TEXT) - .body(is("Invalid URL: null")); + .body(is("Invalid URL Scheme: ")); } } From 5075bca33449a80792948c43dd86bccecf04766a Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 26 May 2024 19:43:59 -0300 Subject: [PATCH 6/9] Formatting with ktlint. --- .editorconfig | 11 +++ .../com/testainers/BasicAuthResource.kt | 42 ++++---- .../kotlin/com/testainers/DelayResource.kt | 40 ++++---- .../kotlin/com/testainers/LengthResource.kt | 95 +++++++++++-------- .../kotlin/com/testainers/MethodsResource.kt | 8 +- .../kotlin/com/testainers/RedirectResource.kt | 49 ++++++---- .../kotlin/com/testainers/ResponseBody.kt | 9 +- .../com/testainers/SimpleHealthCheck.kt | 2 +- .../kotlin/com/testainers/StatusResource.kt | 74 ++++++++------- 9 files changed, 198 insertions(+), 132 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..eff1274 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +insert_final_newline = true + +[{*.kt,*.kts}] +ident_size = 4 +max_line_length = 80 +ktlint_code_style = ktlint_official +ktlint_experimental = enabled +ktlint_standard_no-wildcard-imports = disabled diff --git a/src/main/kotlin/com/testainers/BasicAuthResource.kt b/src/main/kotlin/com/testainers/BasicAuthResource.kt index 0af5028..f26bd13 100644 --- a/src/main/kotlin/com/testainers/BasicAuthResource.kt +++ b/src/main/kotlin/com/testainers/BasicAuthResource.kt @@ -2,7 +2,6 @@ package com.testainers import io.quarkus.security.Authenticated import io.vertx.core.http.HttpServerRequest -import jakarta.inject.Inject import jakarta.ws.rs.* import jakarta.ws.rs.core.* import org.eclipse.microprofile.openapi.annotations.responses.APIResponse @@ -19,39 +18,50 @@ import java.util.* @APIResponses( APIResponse(responseCode = "200"), APIResponse(responseCode = "401"), - APIResponse(responseCode = "403") + APIResponse(responseCode = "403"), ) @Produces(MediaType.APPLICATION_JSON) -class BasicAuthResource(val request: HttpServerRequest, val uriInfo: UriInfo) { - +class BasicAuthResource( + val request: HttpServerRequest, + val uriInfo: UriInfo, +) { @GET fun get( @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, - user: String, pass: String + user: String, + pass: String, ): Response = getResponse(auth, user, pass, null) @POST fun post( @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, - user: String, pass: String, body: Any? + user: String, + pass: String, + body: Any?, ): Response = getResponse(auth, user, pass, body) @PUT fun put( @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, - user: String, pass: String, body: Any? + user: String, + pass: String, + body: Any?, ): Response = getResponse(auth, user, pass, body) @PATCH fun patch( @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, - user: String, pass: String, body: Any? + user: String, + pass: String, + body: Any?, ): Response = getResponse(auth, user, pass, body) @DELETE fun delete( @RestHeader(HttpHeaders.AUTHORIZATION) auth: String?, - user: String, pass: String, body: Any? + user: String, + pass: String, + body: Any?, ): Response = getResponse(auth, user, pass, body) private fun getResponse( @@ -74,12 +84,12 @@ class BasicAuthResource(val request: HttpServerRequest, val uriInfo: UriInfo) { code = 401 bodyMap["message"] = "Authorization header not present." } else { - val encoded = Base64 - .getEncoder() - .encodeToString( - "$user:$pass" - .toByteArray(StandardCharsets.UTF_8) - ) + val encoded = + Base64 + .getEncoder() + .encodeToString( + "$user:$pass".toByteArray(StandardCharsets.UTF_8), + ) val ok = auth == "Basic $encoded" @@ -95,4 +105,4 @@ class BasicAuthResource(val request: HttpServerRequest, val uriInfo: UriInfo) { return Response.status(code).entity(responseBody).build() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/DelayResource.kt b/src/main/kotlin/com/testainers/DelayResource.kt index 81c653f..7fcac50 100644 --- a/src/main/kotlin/com/testainers/DelayResource.kt +++ b/src/main/kotlin/com/testainers/DelayResource.kt @@ -1,7 +1,6 @@ package com.testainers import io.vertx.core.http.HttpServerRequest -import jakarta.inject.Inject import jakarta.ws.rs.* import jakarta.ws.rs.core.* import org.eclipse.microprofile.openapi.annotations.media.Schema @@ -16,52 +15,61 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses @APIResponses( APIResponse(responseCode = "200"), APIResponse(responseCode = "400"), - APIResponse(responseCode = "500") + APIResponse(responseCode = "500"), ) @Produces(MediaType.APPLICATION_JSON) -class DelayResource(val request: HttpServerRequest, val uriInfo: UriInfo) { - +class DelayResource( + val request: HttpServerRequest, + val uriInfo: UriInfo, +) { @GET fun get( @Parameter( description = "Delay must be between 0 and 10 seconds.", - schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") - ) delay: Int + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10"), + ) delay: Int, ): Response = internal(delay, null) @POST fun post( @Parameter( description = "Delay must be between 0 and 10 seconds.", - schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") - ) delay: Int, body: Any? + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10"), + ) delay: Int, + body: Any?, ): Response = internal(delay, body) @PUT fun put( @Parameter( description = "Delay must be between 0 and 10 seconds.", - schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") - ) delay: Int, body: Any? + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10"), + ) delay: Int, + body: Any?, ): Response = internal(delay, body) @PATCH fun patch( @Parameter( description = "Delay must be between 0 and 10 seconds.", - schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") - ) delay: Int, body: Any? + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10"), + ) delay: Int, + body: Any?, ): Response = internal(delay, body) @DELETE fun delete( @Parameter( description = "Delay must be between 0 and 10 seconds.", - schema = Schema(minimum = "0", maximum = "10", defaultValue = "10") - ) delay: Int, body: Any? + schema = Schema(minimum = "0", maximum = "10", defaultValue = "10"), + ) delay: Int, + body: Any?, ): Response = internal(delay, body) - private fun internal(delay: Int, body: Any?): Response { + private fun internal( + delay: Int, + body: Any?, + ): Response { val responseBody = ResponseBody(request, uriInfo, body) var code = 200 @@ -81,4 +89,4 @@ class DelayResource(val request: HttpServerRequest, val uriInfo: UriInfo) { return Response.status(code).entity(responseBody).build() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/LengthResource.kt b/src/main/kotlin/com/testainers/LengthResource.kt index d0e808c..e410699 100644 --- a/src/main/kotlin/com/testainers/LengthResource.kt +++ b/src/main/kotlin/com/testainers/LengthResource.kt @@ -16,8 +16,14 @@ import org.jboss.resteasy.reactive.RestHeader @APIResponses( APIResponse( responseCode = "200", - content = [Content(mediaType = MediaType.TEXT_PLAIN), Content(mediaType = MediaType.APPLICATION_OCTET_STREAM)] - ), APIResponse(responseCode = "500", description = "Invalid size: X.") + content = [ + Content( + mediaType = MediaType.TEXT_PLAIN, + ), + Content(mediaType = MediaType.APPLICATION_OCTET_STREAM), + ], + ), + APIResponse(responseCode = "500", description = "Invalid size: X."), ) class LengthResource { @GET @@ -25,12 +31,13 @@ class LengthResource { @RestHeader(HttpHeaders.ACCEPT) accept: String, @Parameter( description = "Size must be between 1 and 2048.", - schema = Schema( - minimum = "1", - maximum = "2048", - defaultValue = "10" - ) - ) size: Int + schema = + Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10", + ), + ) size: Int, ): Response = internal(accept, size) @POST @@ -38,12 +45,13 @@ class LengthResource { @RestHeader(HttpHeaders.ACCEPT) accept: String, @Parameter( description = "Size must be between 1 and 2048.", - schema = Schema( - minimum = "1", - maximum = "2048", - defaultValue = "10" - ) - ) size: Int + schema = + Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10", + ), + ) size: Int, ): Response = internal(accept, size) @PUT @@ -51,12 +59,13 @@ class LengthResource { @RestHeader(HttpHeaders.ACCEPT) accept: String, @Parameter( description = "Size must be between 1 and 2048.", - schema = Schema( - minimum = "1", - maximum = "2048", - defaultValue = "10" - ) - ) size: Int + schema = + Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10", + ), + ) size: Int, ): Response = internal(accept, size) @PATCH @@ -64,12 +73,13 @@ class LengthResource { @RestHeader(HttpHeaders.ACCEPT) accept: String, @Parameter( description = "Size must be between 1 and 2048.", - schema = Schema( - minimum = "1", - maximum = "2048", - defaultValue = "10" - ) - ) size: Int + schema = + Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10", + ), + ) size: Int, ): Response = internal(accept, size) @DELETE @@ -77,29 +87,34 @@ class LengthResource { @RestHeader(HttpHeaders.ACCEPT) accept: String, @Parameter( description = "Size must be between 1 and 2048.", - schema = Schema( - minimum = "1", - maximum = "2048", - defaultValue = "10" - ) - ) size: Int + schema = + Schema( + minimum = "1", + maximum = "2048", + defaultValue = "10", + ), + ) size: Int, ): Response = internal(accept, size) - private fun internal(accept: String, size: Int): Response = + private fun internal( + accept: String, + size: Int, + ): Response = if (size < 1 || size > 2048) { - Response.status(500) + Response + .status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) .entity(String.format("Invalid size: %d", size)) .build() } else { if (MediaType.APPLICATION_OCTET_STREAM == accept) { - Response.ok( - ByteArray(size), - MediaType.APPLICATION_OCTET_STREAM_TYPE - ).build() + Response + .ok( + ByteArray(size), + MediaType.APPLICATION_OCTET_STREAM_TYPE, + ).build() } else { Response.ok("0".repeat(size), MediaType.TEXT_PLAIN_TYPE).build() } } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/MethodsResource.kt b/src/main/kotlin/com/testainers/MethodsResource.kt index a99c5aa..cc6d2f4 100644 --- a/src/main/kotlin/com/testainers/MethodsResource.kt +++ b/src/main/kotlin/com/testainers/MethodsResource.kt @@ -13,8 +13,10 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses @Path("/methods") @APIResponses(APIResponse(responseCode = "200")) @Produces(MediaType.APPLICATION_JSON) -class MethodsResource(val request: HttpServerRequest, val uriInfo: UriInfo) { - +class MethodsResource( + val request: HttpServerRequest, + val uriInfo: UriInfo, +) { @GET fun get(): ResponseBody = ResponseBody(request, uriInfo, null) @@ -32,4 +34,4 @@ class MethodsResource(val request: HttpServerRequest, val uriInfo: UriInfo) { @DELETE fun delete(body: Any?): ResponseBody = ResponseBody(request, uriInfo, body) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/RedirectResource.kt b/src/main/kotlin/com/testainers/RedirectResource.kt index 0d6349f..2f768b5 100644 --- a/src/main/kotlin/com/testainers/RedirectResource.kt +++ b/src/main/kotlin/com/testainers/RedirectResource.kt @@ -13,73 +13,79 @@ import java.net.URI */ @Path("/redirect") @APIResponses( - APIResponse(responseCode = "30X", description = "Redirected"), APIResponse( + APIResponse(responseCode = "30X", description = "Redirected"), + APIResponse( responseCode = "500", - description = "'Invalid URL' or 'Invalid URL Scheme' or 'Invalid status code: 30X'" - ) + description = + "'Invalid URL' or 'Invalid URL Scheme' " + + "or 'Invalid status code: 30X'", + ), ) class RedirectResource { @GET fun get( @Parameter( description = "URL to redirect.", - required = true + required = true, ) @QueryParam("url") @DefaultValue("") url: String, @Parameter( description = "Response status code.", - schema = Schema(minimum = "300", maximum = "399") - ) @QueryParam("code") @DefaultValue("302") code: Int + schema = Schema(minimum = "300", maximum = "399"), + ) @QueryParam("code") @DefaultValue("302") code: Int, ): Response = internal(url, code) @POST fun post( @Parameter( description = "URL to redirect.", - required = true + required = true, ) @QueryParam("url") @DefaultValue("") url: String, @Parameter( description = "Response status code.", - schema = Schema(minimum = "300", maximum = "399") - ) @QueryParam("code") @DefaultValue("302") code: Int + schema = Schema(minimum = "300", maximum = "399"), + ) @QueryParam("code") @DefaultValue("302") code: Int, ): Response = internal(url, code) @PUT fun put( @Parameter( description = "URL to redirect.", - required = true + required = true, ) @QueryParam("url") @DefaultValue("") url: String, @Parameter( description = "Response status code.", - schema = Schema(minimum = "300", maximum = "399") - ) @QueryParam("code") @DefaultValue("302") code: Int + schema = Schema(minimum = "300", maximum = "399"), + ) @QueryParam("code") @DefaultValue("302") code: Int, ): Response = internal(url, code) @PATCH fun patch( @Parameter( description = "URL to redirect.", - required = true + required = true, ) @QueryParam("url") @DefaultValue("") url: String, @Parameter( description = "Response status code.", - schema = Schema(minimum = "300", maximum = "399") - ) @QueryParam("code") @DefaultValue("302") code: Int + schema = Schema(minimum = "300", maximum = "399"), + ) @QueryParam("code") @DefaultValue("302") code: Int, ): Response = internal(url, code) @DELETE fun delete( @Parameter( description = "URL to redirect.", - required = true + required = true, ) @QueryParam("url") @DefaultValue("") url: String, @Parameter( description = "Response status code.", - schema = Schema(minimum = "300", maximum = "399") - ) @QueryParam("code") @DefaultValue("302") code: Int + schema = Schema(minimum = "300", maximum = "399"), + ) @QueryParam("code") @DefaultValue("302") code: Int, ): Response = internal(url, code) - private fun internal(url: String, code: Int): Response { + private fun internal( + url: String, + code: Int, + ): Response { val uri: URI try { @@ -108,8 +114,9 @@ class RedirectResource { .build() } - return Response.status(code) + return Response + .status(code) .header(HttpHeaders.LOCATION, uri.toASCIIString()) .build() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/ResponseBody.kt b/src/main/kotlin/com/testainers/ResponseBody.kt index 62f35a2..b6c01a4 100644 --- a/src/main/kotlin/com/testainers/ResponseBody.kt +++ b/src/main/kotlin/com/testainers/ResponseBody.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + package com.testainers import io.quarkus.runtime.annotations.RegisterForReflection @@ -10,8 +12,9 @@ import java.net.URI */ @RegisterForReflection class ResponseBody( - request: HttpServerRequest, uriInfo: UriInfo, - body: Any? + request: HttpServerRequest, + uriInfo: UriInfo, + body: Any?, ) { var uri: URI var method: String = request.method().name() @@ -34,4 +37,4 @@ class ResponseBody( this.body = body } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/SimpleHealthCheck.kt b/src/main/kotlin/com/testainers/SimpleHealthCheck.kt index 9879c4e..997bcce 100644 --- a/src/main/kotlin/com/testainers/SimpleHealthCheck.kt +++ b/src/main/kotlin/com/testainers/SimpleHealthCheck.kt @@ -11,4 +11,4 @@ import org.eclipse.microprofile.health.* class SimpleHealthCheck : HealthCheck { override fun call(): HealthCheckResponse = HealthCheckResponse.up("httpbucket") -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/testainers/StatusResource.kt b/src/main/kotlin/com/testainers/StatusResource.kt index 2d71225..d380a62 100644 --- a/src/main/kotlin/com/testainers/StatusResource.kt +++ b/src/main/kotlin/com/testainers/StatusResource.kt @@ -11,68 +11,78 @@ import org.eclipse.microprofile.openapi.annotations.parameters.Parameter */ @Path("/status/{code}") @Produces(MediaType.APPLICATION_JSON) -class StatusResource(val request: HttpServerRequest, val uriInfo: UriInfo) { +class StatusResource( + val request: HttpServerRequest, + val uriInfo: UriInfo, +) { @GET fun get( @Parameter( - description = "Code must be between 200 and 599. " + + description = + "Code must be between 200 and 599. " + "Informational responses (1XX) are not supported.", - schema = Schema(minimum = "200", maximum = "599") - ) code: Int - ): Response { - return internal(code, null) - } + schema = Schema(minimum = "200", maximum = "599"), + ) code: Int, + ): Response = internal(code, null) @POST fun post( @Parameter( - description = "Code must be between 200 and 599. " + + description = + "Code must be between 200 and 599. " + "Informational responses (1XX) are not supported.", - schema = Schema(minimum = "200", maximum = "599") - ) code: Int, body: Any? - ): Response { - return internal(code, body) - } + schema = Schema(minimum = "200", maximum = "599"), + ) code: Int, + body: Any?, + ): Response = internal(code, body) @PUT fun put( @Parameter( - description = "Code must be between 200 and 599. " + + description = + "Code must be between 200 and 599. " + "Informational responses (1XX) are not supported.", - schema = Schema(minimum = "200", maximum = "599") - ) code: Int, body: Any? - ): Response { - return internal(code, body) - } + schema = Schema(minimum = "200", maximum = "599"), + ) code: Int, + body: Any?, + ): Response = internal(code, body) @PATCH fun patch( @Parameter( - description = "Code must be between 200 and 599. " + + description = + "Code must be between 200 and 599. " + "Informational responses (1XX) are not supported.", - schema = Schema(minimum = "200", maximum = "599") - ) code: Int, body: Any? + schema = Schema(minimum = "200", maximum = "599"), + ) code: Int, + body: Any?, ): Response = internal(code, body) @DELETE fun delete( @Parameter( - description = "Code must be between 200 and 599. " + + description = + "Code must be between 200 and 599. " + "Informational responses (1XX) are not supported.", - schema = Schema(minimum = "200", maximum = "599") - ) code: Int, body: Any? + schema = Schema(minimum = "200", maximum = "599"), + ) code: Int, + body: Any?, ): Response = internal(code, body) - private fun internal(code: Int, body: Any?): Response { + private fun internal( + code: Int, + body: Any?, + ): Response { var status = code val responseBody = ResponseBody(request, uriInfo, body) if (status < 200 || status > 599) { - val message = if (status in 100..199) { - "Informational responses are not supported: %d" - } else { - "Unknown status code: %d" - } + val message = + if (status in 100..199) { + "Informational responses are not supported: %d" + } else { + "Unknown status code: %d" + } responseBody.body = String.format(message, status) status = 500 @@ -80,4 +90,4 @@ class StatusResource(val request: HttpServerRequest, val uriInfo: UriInfo) { return Response.status(status).entity(responseBody).build() } -} \ No newline at end of file +} From 502e225e0d3fefa45cdada16b6aa3e161ce0a82c Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Thu, 30 May 2024 17:00:34 -0300 Subject: [PATCH 7/9] Writing tests in kotlin. --- gradle.properties | 4 +- .../com/testainers/BasicAuthResourceIT.java | 11 - .../java/com/testainers/DelayResourceIT.java | 11 - .../java/com/testainers/LengthResourceIT.java | 11 - .../com/testainers/MethodsResourceIT.java | 11 - .../com/testainers/RedirectResourceIT.java | 11 - .../java/com/testainers/StatusResourceIT.java | 11 - .../com/testainers/BasicAuthResourceIT.kt | 11 + .../kotlin/com/testainers/DelayResourceIT.kt | 11 + .../kotlin/com/testainers/LengthResourceIT.kt | 11 + .../com/testainers/MethodsResourceIT.kt | 11 + .../com/testainers/RedirectResourceIT.kt | 11 + .../kotlin/com/testainers/StatusResourceIT.kt | 11 + .../java/com/testainers/BaseResourceTest.java | 87 -------- .../com/testainers/BasicAuthResourceTest.java | 130 ------------ .../com/testainers/DelayResourceTest.java | 111 ----------- .../com/testainers/LengthResourceTest.java | 90 --------- .../com/testainers/MethodsResourceTest.java | 68 ------- .../com/testainers/RedirectResourceTest.java | 135 ------------- .../com/testainers/StatusResourceTest.java | 162 --------------- .../kotlin/com/testainers/BaseResourceTest.kt | 91 +++++++++ .../com/testainers/BasicAuthResourceTest.kt | 95 +++++++++ .../com/testainers/DelayResourceTest.kt | 117 +++++++++++ .../com/testainers/LengthResourceTest.kt | 92 +++++++++ .../com/testainers/MethodsResourceTest.kt | 70 +++++++ .../com/testainers/RedirectResourceTest.kt | 151 ++++++++++++++ .../com/testainers/StatusResourceTest.kt | 188 ++++++++++++++++++ 27 files changed, 872 insertions(+), 851 deletions(-) delete mode 100644 src/native-test/java/com/testainers/BasicAuthResourceIT.java delete mode 100644 src/native-test/java/com/testainers/DelayResourceIT.java delete mode 100644 src/native-test/java/com/testainers/LengthResourceIT.java delete mode 100644 src/native-test/java/com/testainers/MethodsResourceIT.java delete mode 100644 src/native-test/java/com/testainers/RedirectResourceIT.java delete mode 100644 src/native-test/java/com/testainers/StatusResourceIT.java create mode 100644 src/native-test/kotlin/com/testainers/BasicAuthResourceIT.kt create mode 100644 src/native-test/kotlin/com/testainers/DelayResourceIT.kt create mode 100644 src/native-test/kotlin/com/testainers/LengthResourceIT.kt create mode 100644 src/native-test/kotlin/com/testainers/MethodsResourceIT.kt create mode 100644 src/native-test/kotlin/com/testainers/RedirectResourceIT.kt create mode 100644 src/native-test/kotlin/com/testainers/StatusResourceIT.kt delete mode 100644 src/test/java/com/testainers/BaseResourceTest.java delete mode 100644 src/test/java/com/testainers/BasicAuthResourceTest.java delete mode 100644 src/test/java/com/testainers/DelayResourceTest.java delete mode 100644 src/test/java/com/testainers/LengthResourceTest.java delete mode 100644 src/test/java/com/testainers/MethodsResourceTest.java delete mode 100644 src/test/java/com/testainers/RedirectResourceTest.java delete mode 100644 src/test/java/com/testainers/StatusResourceTest.java create mode 100644 src/test/kotlin/com/testainers/BaseResourceTest.kt create mode 100644 src/test/kotlin/com/testainers/BasicAuthResourceTest.kt create mode 100644 src/test/kotlin/com/testainers/DelayResourceTest.kt create mode 100644 src/test/kotlin/com/testainers/LengthResourceTest.kt create mode 100644 src/test/kotlin/com/testainers/MethodsResourceTest.kt create mode 100644 src/test/kotlin/com/testainers/RedirectResourceTest.kt create mode 100644 src/test/kotlin/com/testainers/StatusResourceTest.kt diff --git a/gradle.properties b/gradle.properties index 4a11163..b2d908d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ #Gradle properties quarkusPluginId=io.quarkus -quarkusPluginVersion=3.10.2 +quarkusPluginVersion=3.11.0 quarkusPlatformGroupId=io.quarkus.platform quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.10.2 \ No newline at end of file +quarkusPlatformVersion=3.11.0 diff --git a/src/native-test/java/com/testainers/BasicAuthResourceIT.java b/src/native-test/java/com/testainers/BasicAuthResourceIT.java deleted file mode 100644 index 3f7e734..0000000 --- a/src/native-test/java/com/testainers/BasicAuthResourceIT.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -/** - * @author Eduardo Folly - */ -@QuarkusIntegrationTest -public class BasicAuthResourceIT extends BasicAuthResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/src/native-test/java/com/testainers/DelayResourceIT.java b/src/native-test/java/com/testainers/DelayResourceIT.java deleted file mode 100644 index a6be142..0000000 --- a/src/native-test/java/com/testainers/DelayResourceIT.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -/** - * @author Eduardo Folly - */ -@QuarkusIntegrationTest -public class DelayResourceIT extends DelayResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/src/native-test/java/com/testainers/LengthResourceIT.java b/src/native-test/java/com/testainers/LengthResourceIT.java deleted file mode 100644 index 990d64e..0000000 --- a/src/native-test/java/com/testainers/LengthResourceIT.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -/** - * @author Eduardo Folly - */ -@QuarkusIntegrationTest -public class LengthResourceIT extends LengthResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/src/native-test/java/com/testainers/MethodsResourceIT.java b/src/native-test/java/com/testainers/MethodsResourceIT.java deleted file mode 100644 index 025c846..0000000 --- a/src/native-test/java/com/testainers/MethodsResourceIT.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -/** - * @author Eduardo Folly - */ -@QuarkusIntegrationTest -public class MethodsResourceIT extends MethodsResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/src/native-test/java/com/testainers/RedirectResourceIT.java b/src/native-test/java/com/testainers/RedirectResourceIT.java deleted file mode 100644 index 7eb6bef..0000000 --- a/src/native-test/java/com/testainers/RedirectResourceIT.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -/** - * @author Eduardo Folly - */ -@QuarkusIntegrationTest -public class RedirectResourceIT extends RedirectResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/src/native-test/java/com/testainers/StatusResourceIT.java b/src/native-test/java/com/testainers/StatusResourceIT.java deleted file mode 100644 index 7060bf1..0000000 --- a/src/native-test/java/com/testainers/StatusResourceIT.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -/** - * @author Eduardo Folly - */ -@QuarkusIntegrationTest -public class StatusResourceIT extends StatusResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/src/native-test/kotlin/com/testainers/BasicAuthResourceIT.kt b/src/native-test/kotlin/com/testainers/BasicAuthResourceIT.kt new file mode 100644 index 0000000..8a65277 --- /dev/null +++ b/src/native-test/kotlin/com/testainers/BasicAuthResourceIT.kt @@ -0,0 +1,11 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusIntegrationTest + +/** + * @author Eduardo Folly + */ +@QuarkusIntegrationTest +class BasicAuthResourceIT : BasicAuthResourceTest() { + // This class is empty, but it is necessary to run the tests. +} diff --git a/src/native-test/kotlin/com/testainers/DelayResourceIT.kt b/src/native-test/kotlin/com/testainers/DelayResourceIT.kt new file mode 100644 index 0000000..381dbcf --- /dev/null +++ b/src/native-test/kotlin/com/testainers/DelayResourceIT.kt @@ -0,0 +1,11 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusIntegrationTest + +/** + * @author Eduardo Folly + */ +@QuarkusIntegrationTest +class DelayResourceIT : DelayResourceTest() { + // This class is empty, but it is necessary to run the tests. +} diff --git a/src/native-test/kotlin/com/testainers/LengthResourceIT.kt b/src/native-test/kotlin/com/testainers/LengthResourceIT.kt new file mode 100644 index 0000000..e99576c --- /dev/null +++ b/src/native-test/kotlin/com/testainers/LengthResourceIT.kt @@ -0,0 +1,11 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusIntegrationTest + +/** + * @author Eduardo Folly + */ +@QuarkusIntegrationTest +class LengthResourceIT : LengthResourceTest() { + // This class is empty, but it is necessary to run the tests. +} diff --git a/src/native-test/kotlin/com/testainers/MethodsResourceIT.kt b/src/native-test/kotlin/com/testainers/MethodsResourceIT.kt new file mode 100644 index 0000000..7cfe510 --- /dev/null +++ b/src/native-test/kotlin/com/testainers/MethodsResourceIT.kt @@ -0,0 +1,11 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusIntegrationTest + +/** + * @author Eduardo Folly + */ +@QuarkusIntegrationTest +class MethodsResourceIT : MethodsResourceTest() { + // This class is empty, but it is necessary to run the tests. +} diff --git a/src/native-test/kotlin/com/testainers/RedirectResourceIT.kt b/src/native-test/kotlin/com/testainers/RedirectResourceIT.kt new file mode 100644 index 0000000..ad961b0 --- /dev/null +++ b/src/native-test/kotlin/com/testainers/RedirectResourceIT.kt @@ -0,0 +1,11 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusIntegrationTest + +/** + * @author Eduardo Folly + */ +@QuarkusIntegrationTest +class RedirectResourceIT : RedirectResourceTest() { + // This class is empty, but it is necessary to run the tests. +} diff --git a/src/native-test/kotlin/com/testainers/StatusResourceIT.kt b/src/native-test/kotlin/com/testainers/StatusResourceIT.kt new file mode 100644 index 0000000..947de2a --- /dev/null +++ b/src/native-test/kotlin/com/testainers/StatusResourceIT.kt @@ -0,0 +1,11 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusIntegrationTest + +/** + * @author Eduardo Folly + */ +@QuarkusIntegrationTest +class StatusResourceIT : StatusResourceTest() { + // This class is empty, but it is necessary to run the tests. +} diff --git a/src/test/java/com/testainers/BaseResourceTest.java b/src/test/java/com/testainers/BaseResourceTest.java deleted file mode 100644 index a0bfe3e..0000000 --- a/src/test/java/com/testainers/BaseResourceTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.testainers; - -import io.restassured.RestAssured; -import io.restassured.config.LogConfig; -import io.restassured.config.RestAssuredConfig; -import io.restassured.http.ContentType; -import io.restassured.http.Method; -import io.restassured.specification.RequestSpecification; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import static io.restassured.RestAssured.given; -import static io.restassured.config.RedirectConfig.redirectConfig; -import static org.hamcrest.Matchers.*; - -/** - * @author Eduardo Folly - */ -public class BaseResourceTest { - - protected static final List METHODS = - List.of(Method.GET, Method.POST, Method.PUT, - Method.PATCH, Method.DELETE); - - protected static final RestAssuredConfig CONFIG = RestAssured - .config() - .logConfig( - LogConfig - .logConfig() - .enableLoggingOfRequestAndResponseIfValidationFails() - .enablePrettyPrinting(true) - ) - .redirect(redirectConfig().followRedirects(false)); - - protected static final Map HEADERS = - Map.of("test-header", List.of("test-header-value"), - "test-header-2", List.of("test-header-value-2")); - - protected static final Map QUERY_PARAMS = - Map.of("test-qs", List.of("test-qs")); - - protected static final Map BODY = - Map.of("test_string", "test", - "test_int", 1, - "test_boolean", true); - - protected RequestSpecification json(Method method) { - RequestSpecification spec = given() - .config(CONFIG) - .when() - .headers(HEADERS) - .queryParams(QUERY_PARAMS) - .contentType(ContentType.JSON); - - if (method != null && method != Method.GET) { - spec = spec.body(BODY); - } - - return spec; - } - - protected Object[] bodyMatchers(Method method, Object... objects) { - List matchers = new ArrayList<>(); - - matchers.add("method"); - matchers.add(is(method.name())); - - matchers.add("queryParameters"); - matchers.add(is(QUERY_PARAMS)); - - matchers.add("headers"); - matchers.add(aMapWithSize(greaterThanOrEqualTo(HEADERS.size()))); - - matchers.add("headers"); - matchers.add(hasEntry(in(HEADERS.keySet()), in(HEADERS.values()))); - - if (objects != null && objects.length > 0) { - matchers.addAll(Arrays.asList(objects)); - } - - return matchers.toArray(); - } - -} diff --git a/src/test/java/com/testainers/BasicAuthResourceTest.java b/src/test/java/com/testainers/BasicAuthResourceTest.java deleted file mode 100644 index e74bfa9..0000000 --- a/src/test/java/com/testainers/BasicAuthResourceTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.http.Method; -import jakarta.ws.rs.core.HttpHeaders; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.Matchers.*; - -/** - * @author Eduardo Folly - */ -@QuarkusTest -public class BasicAuthResourceTest extends BaseResourceTest { - - private static final String user = "test"; - private static final String pass = "test-pass0"; - - @Test - public void noHeader() { - for (Method method : METHODS) { - json(method) - .pathParam("user", user) - .pathParam("pass", pass) - .request(method, "/basic-auth/{user}/{pass}") - .then() - .statusCode(401) - .body("body.body", - method == Method.GET ? nullValue() : is(BODY), - bodyMatchers( - method, - "body.auth", is(false), - "body.user", is(user), - "body.pass", is(pass), - "body.message", - is("Authorization header not present.") - )); - } - } - - @Test - public void emptyHeader() { - for (Method method : METHODS) { - json(method) - .header(HttpHeaders.AUTHORIZATION, "") - .pathParam("user", user) - .pathParam("pass", pass) - .request(method, "/basic-auth/{user}/{pass}") - .then() - .statusCode(401) - .body("body.body", - method == Method.GET ? nullValue() : is(BODY), - bodyMatchers( - method, - "body.auth", is(false), - "body.user", is(user), - "body.pass", is(pass), - "body.message", - is("Authorization header not present.") - )); - } - } - - @Test - public void success() { - for (Method method : METHODS) { - json(method) - .auth().preemptive().basic(user, pass) - .pathParam("user", user) - .pathParam("pass", pass) - .request(method, "/basic-auth/{user}/{pass}") - .then() - .statusCode(200) - .body("body.body", - method == Method.GET ? nullValue() : is(BODY), - bodyMatchers( - method, - "body.auth", is(true), - "body.user", is(user), - "body.pass", is(pass), - "body.message", is("Success.") - )); - } - } - - @Test - public void userFail() { - for (Method method : METHODS) { - json(method) - .auth().preemptive().basic(user, pass) - .pathParam("user", user + "Fail") - .pathParam("pass", pass) - .request(method, "/basic-auth/{user}/{pass}") - .then() - .statusCode(403) - .body("body.body", - method == Method.GET ? nullValue() : is(BODY), - bodyMatchers( - method, - "body.auth", is(false), - "body.user", is(user + "Fail"), - "body.pass", is(pass), - "body.message", is("Forbidden.") - )); - } - } - - @Test - public void passFail() { - for (Method method : METHODS) { - json(method) - .auth().preemptive().basic(user, pass) - .pathParam("user", user) - .pathParam("pass", pass + "Fail") - .request(method, "/basic-auth/{user}/{pass}") - .then() - .statusCode(403) - .body("body.body", - method == Method.GET ? nullValue() : is(BODY), - bodyMatchers( - method, - "body.auth", is(false), - "body.user", is(user), - "body.pass", is(pass + "Fail"), - "body.message", is("Forbidden.") - )); - } - } - -} diff --git a/src/test/java/com/testainers/DelayResourceTest.java b/src/test/java/com/testainers/DelayResourceTest.java deleted file mode 100644 index fa68e11..0000000 --- a/src/test/java/com/testainers/DelayResourceTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.http.Method; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.Matchers.containsStringIgnoringCase; -import static org.hamcrest.Matchers.is; - -/** - * @author Eduardo Folly - */ -@QuarkusTest -public class DelayResourceTest extends BaseResourceTest { - - @Test - public void delayEmpty() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")); - } - } - - @Test - public void delayString() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/a") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")); - } - } - - @Test - public void delayDouble() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/1.8") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")); - } - } - - @Test - public void delayNegative() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/-1") - .then() - .statusCode(400) - .body("body", is("Invalid delay: -1"), - bodyMatchers(method)); - } - } - - @Test - public void delay0() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/0") - .then() - .statusCode(200) - .body("body", is("Slept for 0 seconds."), - bodyMatchers(method)); - } - } - - @Test - public void delay1() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/1") - .then() - .statusCode(200) - .body("body", is("Slept for 1 seconds."), - bodyMatchers(method)); - } - } - - @Test - @Disabled - public void delay10() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/10") - .then() - .statusCode(200) - .body("body", is("Slept for 10 seconds."), - bodyMatchers(method)); - } - } - - @Test - public void delay11() { - for (Method method : METHODS) { - json(method) - .request(method, "/delay/11") - .then() - .statusCode(400) - .body("body", is("Invalid delay: 11"), - bodyMatchers(method)); - } - } - -} diff --git a/src/test/java/com/testainers/LengthResourceTest.java b/src/test/java/com/testainers/LengthResourceTest.java deleted file mode 100644 index 4d37598..0000000 --- a/src/test/java/com/testainers/LengthResourceTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.http.ContentType; -import io.restassured.http.Method; -import jakarta.ws.rs.core.MediaType; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.*; - -/** - * @author Eduardo Folly - */ -@QuarkusTest -public class LengthResourceTest { - - static final List METHODS = - List.of(Method.GET, Method.POST, Method.PUT, - Method.PATCH, Method.DELETE); - - @Test - public void length0() { - for (Method method : METHODS) { - given().when() - .request(method, "/length/0") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid size: 0")); - } - } - - @Test - public void length5() { - for (Method method : METHODS) { - given().when() - .request(method, "/length/5") - .then() - .statusCode(200) - .contentType(ContentType.TEXT) - .header("content-length", "5") - .body(is("00000")); - } - } - - @Test - public void length10() { - for (Method method : METHODS) { - given().when() - .accept(MediaType.TEXT_PLAIN) - .request(method, "/length/10") - .then() - .statusCode(200) - .contentType(ContentType.TEXT) - .header("content-length", "10") - .body(is("0000000000")); - } - } - - @Test - public void length1024() { - for (Method method : METHODS) { - given().when() - .accept(MediaType.APPLICATION_OCTET_STREAM) - .request(method, "/length/1024") - .then() - .statusCode(200) - .contentType(ContentType.BINARY) - .header("content-length", "1024") - .body(notNullValue()); - } - } - - @Test - public void length2049() { - for (Method method : METHODS) { - given().when() - .accept(MediaType.APPLICATION_OCTET_STREAM) - .request(method, "/length/2049") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid size: 2049")); - } - } - -} \ No newline at end of file diff --git a/src/test/java/com/testainers/MethodsResourceTest.java b/src/test/java/com/testainers/MethodsResourceTest.java deleted file mode 100644 index 124648f..0000000 --- a/src/test/java/com/testainers/MethodsResourceTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.http.Method; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.Matchers.*; - -/** - * @author Eduardo Folly - */ -@QuarkusTest -public class MethodsResourceTest extends BaseResourceTest { - - @Test - public void methodsGet() { - json(Method.GET) - .get("/methods") - .then() - .statusCode(200) - .body("", not(""), bodyMatchers(Method.GET)); - } - - @Test - public void methodsHead() { - json(Method.HEAD) - .head("/methods") - .then() - .statusCode(200); - } - - @Test - public void methodsPost() { - json(Method.POST) - .post("/methods") - .then() - .statusCode(200) - .body("body", is(BODY), bodyMatchers(Method.POST)); - } - - @Test - public void methodsPut() { - json(Method.PUT) - .put("/methods") - .then() - .statusCode(200) - .body("body", is(BODY), bodyMatchers(Method.PUT)); - } - - @Test - public void methodsPatch() { - json(Method.PATCH) - .patch("/methods") - .then() - .statusCode(200) - .body("body", is(BODY), bodyMatchers(Method.PATCH)); - } - - @Test - public void methodsDelete() { - json(Method.DELETE) - .delete("/methods") - .then() - .statusCode(200) - .body("body", is(BODY), bodyMatchers(Method.DELETE)); - } - -} \ No newline at end of file diff --git a/src/test/java/com/testainers/RedirectResourceTest.java b/src/test/java/com/testainers/RedirectResourceTest.java deleted file mode 100644 index b91832b..0000000 --- a/src/test/java/com/testainers/RedirectResourceTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.RestAssured; -import io.restassured.config.RestAssuredConfig; -import io.restassured.http.ContentType; -import io.restassured.http.Method; -import jakarta.ws.rs.core.HttpHeaders; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static io.restassured.RestAssured.given; -import static io.restassured.config.RedirectConfig.redirectConfig; -import static org.hamcrest.Matchers.is; - -/** - * @author Eduardo Folly - */ -@QuarkusTest -public class RedirectResourceTest { - - static final String LOCATION = "https://testainers.com"; - - static final String INVALID_SCHEME = "testainers.com"; - - static final List METHODS = - List.of(Method.GET, Method.POST, Method.PUT, - Method.PATCH, Method.DELETE); - - static final RestAssuredConfig CONFIG = RestAssured - .config() - .redirect(redirectConfig().followRedirects(false)); - - @Test - public void redirect302() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .queryParam("url", LOCATION) - .request(method, "/redirect") - .then() - .statusCode(302) - .header(HttpHeaders.LOCATION, LOCATION) - .header(HttpHeaders.CONTENT_LENGTH, "0"); - } - } - - @Test - public void redirect303() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .queryParam("url", LOCATION) - .queryParam("code", 303) - .request(method, "/redirect") - .then() - .statusCode(303) - .header(HttpHeaders.LOCATION, LOCATION) - .header(HttpHeaders.CONTENT_LENGTH, "0"); - } - } - - @Test - public void redirect299() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .queryParam("url", LOCATION) - .queryParam("code", 299) - .request(method, "/redirect") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid status code: 299")); - } - } - - @Test - public void redirect499() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .queryParam("url", LOCATION) - .queryParam("code", 499) - .request(method, "/redirect") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid status code: 499")); - } - } - - @Test - public void redirectNoUrl() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .request(method, "/redirect") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid URL Scheme: ")); - } - } - - @Test - public void redirectEmptyUrl() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .queryParam("url", "") - .request(method, "/redirect") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid URL Scheme: ")); - } - } - - @Test - public void redirectInvalidScheme() { - for (Method method : METHODS) { - given().config(CONFIG) - .when() - .queryParam("url", INVALID_SCHEME) - .request(method, "/redirect") - .then() - .statusCode(500) - .contentType(ContentType.TEXT) - .body(is("Invalid URL Scheme: " + INVALID_SCHEME)); - } - } - -} \ No newline at end of file diff --git a/src/test/java/com/testainers/StatusResourceTest.java b/src/test/java/com/testainers/StatusResourceTest.java deleted file mode 100644 index b892dc5..0000000 --- a/src/test/java/com/testainers/StatusResourceTest.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.testainers; - -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.http.Method; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.Matchers.*; - -/** - * @author Eduardo Folly - */ -@QuarkusTest -public class StatusResourceTest extends BaseResourceTest { - - @Test - public void statusString() { - for (Method method : METHODS) { - json(method) - .request(method, "/status/a") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")); - } - } - - @Test - public void statusDouble() { - for (Method method : METHODS) { - json(method) - .request(method, "/status/1.8") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")); - } - } - - @Test - public void statusNegative() { - for (Method method : METHODS) { - json(method).request(method, "/status/-1") - .then() - .statusCode(500) - .body("body", is("Unknown status code: -1"), - bodyMatchers(method)); - } - } - - @Test - public void status0() { - for (Method method : METHODS) { - json(method).request(method, "/status/0") - .then() - .statusCode(500) - .body("body", is("Unknown status code: 0"), - bodyMatchers(method)); - } - } - - @Test - public void status99() { - for (Method method : METHODS) { - json(method).request(method, "/status/99") - .then() - .statusCode(500) - .body("body", is("Unknown status code: 99"), - bodyMatchers(method)); - } - } - - @Test - public void status100() { - for (Method method : METHODS) { - json(method).request(method, "/status/100") - .then() - .statusCode(500) - .body("body", - is("Informational responses are not supported: 100"), - bodyMatchers(method)); - } - } - - @Test - public void status199() { - for (Method method : METHODS) { - json(method) - .request(method, "/status/199") - .then() - .statusCode(500) - .body("body", - is("Informational responses are not supported: 199"), - bodyMatchers(method)); - } - } - - @Test - public void status200() { - for (Method method : METHODS) { - json(method) - .request(method, "/status/200") - .then() - .statusCode(200) - .body("body", - method == Method.GET ? not(BODY) : is(BODY), - bodyMatchers(method)); - } - } - - @Test - public void status204() { - for (Method method : METHODS) { - json(method).request(method, "/status/204") - .then() - .statusCode(204) - .body(is("")); - } - } - - @Test - public void status205() { - for (Method method : METHODS) { - json(method).request(method, "/status/205") - .then() - .statusCode(205) - .headers("content-length", "0") - .body(is("")); - } - } - - @Test - public void status304() { - for (Method method : METHODS) { - json(method).request(method, "/status/304") - .then() - .statusCode(304) - .body(is("")); - } - } - - @Test - public void status599() { - for (Method method : METHODS) { - json(method).request(method, "/status/599") - .then() - .statusCode(599) - .body("body", - method == Method.GET ? not(BODY) : is(BODY), - bodyMatchers(method)); - } - } - - @Test - public void status600() { - for (Method method : METHODS) { - json(method).request(method, "/status/600") - .then() - .statusCode(500) - .body("body", is("Unknown status code: 600"), - bodyMatchers(method)); - } - } - -} diff --git a/src/test/kotlin/com/testainers/BaseResourceTest.kt b/src/test/kotlin/com/testainers/BaseResourceTest.kt new file mode 100644 index 0000000..6e78a0b --- /dev/null +++ b/src/test/kotlin/com/testainers/BaseResourceTest.kt @@ -0,0 +1,91 @@ +package com.testainers + +import io.restassured.RestAssured +import io.restassured.config.* +import io.restassured.http.ContentType +import io.restassured.http.Method +import io.restassured.specification.RequestSpecification +import org.hamcrest.Matcher +import org.hamcrest.Matchers.* + +/** + * @author Eduardo Folly + */ +abstract class BaseResourceTest { + protected val methods = + listOf(Method.GET, Method.POST, Method.PUT, Method.DELETE) + + private val config: RestAssuredConfig = + RestAssured + .config() + .logConfig( + LogConfig + .logConfig() + .enableLoggingOfRequestAndResponseIfValidationFails() + .enablePrettyPrinting(true), + ).redirect( + RedirectConfig.redirectConfig().followRedirects(false), + ) + + private val headers = + mapOf( + "test-header" to listOf("test-header-value"), + "test-header-2" to listOf("test-header-value-2"), + ) + + private val queryParams = mapOf("test-qs" to listOf("test-qs-value")) + + protected val body = + mapOf( + "test-string" to "test", + "test-int" to 1, + "test-boolean" to true, + ) + + fun json(method: Method): RequestSpecification { + val spec = + RestAssured + .given() + .config(config) + .`when`() + .headers(headers) + .queryParams(queryParams) + .contentType(ContentType.JSON) + + if (method != Method.GET) { + spec.body(body) + } + + return spec + } + + fun bodyMatchers( + method: Method, + args: Map> = emptyMap(), + ): Array { + val matchers = mutableListOf() + + matchers.add("method") + matchers.add(equalTo(method.name)) + + matchers.add("queryParameters") + matchers.add(equalTo(queryParams)) + + matchers.add("headers") + matchers.add( + aMapWithSize(greaterThanOrEqualTo(headers.size)), + ) + + matchers.add("headers") + matchers.add(hasEntry(`in`(headers.keys), `in`(headers.values))) + + if (args.isNotEmpty()) { + args.forEach { (key, value) -> + matchers.add(key) + matchers.add(value) + } + } + + return matchers.toTypedArray() + } +} diff --git a/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt b/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt new file mode 100644 index 0000000..6d6ee33 --- /dev/null +++ b/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt @@ -0,0 +1,95 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.Method +import jakarta.ws.rs.core.HttpHeaders +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +/** + * @author Eduardo Folly + */ +@QuarkusTest +class BasicAuthResourceTest : BaseResourceTest() { + val user = "test" + val pass = "test-pass0" + + @Test + fun noHeader() { + methods.forEach { + json(it) + .request(it, "/basic-auth/$user/$pass") + .then() + .statusCode(401) + .body( + "body.body", + if (it == Method.GET) nullValue() else equalTo(body), + *bodyMatchers( + it, + mapOf( + "body.auth" to equalTo(false), + "body.user" to equalTo(user), + "body.pass" to equalTo(pass), + "body.message" to + equalTo( + "Authorization header not present.", + ), + ), + ), + ) + } + } + + @Test + fun emptyHeader() { + methods.forEach { + json(it) + .header(HttpHeaders.AUTHORIZATION, "") + .request(it, "/basic-auth/$user/$pass") + .then() + .statusCode(401) + .body( + "body.body", + if (it == Method.GET) nullValue() else equalTo(body), + *bodyMatchers( + it, + mapOf( + "body.auth" to equalTo(false), + "body.user" to equalTo(user), + "body.pass" to equalTo(pass), + "body.message" to + equalTo( + "Authorization header not present.", + ), + ), + ), + ) + } + } + + @Test + fun success() { + methods.forEach { + json(it) + .auth() + .preemptive() + .basic(user, pass) + .request(it, "/basic-auth/$user/$pass") + .then() + .statusCode(200) + .body( + "body.body", + if (it == Method.GET) nullValue() else equalTo(body), + *bodyMatchers( + it, + mapOf( + "body.auth" to equalTo(true), + "body.user" to equalTo(user), + "body.pass" to equalTo(pass), + "body.message" to equalTo("Success."), + ), + ), + ) + } + } +} diff --git a/src/test/kotlin/com/testainers/DelayResourceTest.kt b/src/test/kotlin/com/testainers/DelayResourceTest.kt new file mode 100644 index 0000000..16a2900 --- /dev/null +++ b/src/test/kotlin/com/testainers/DelayResourceTest.kt @@ -0,0 +1,117 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusTest +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +/** + * @author Eduardo Folly + */ +@QuarkusTest +class DelayResourceTest : BaseResourceTest() { + @Test + fun delayEmpty() { + methods.forEach { + json(it) + .request(it, "/delay") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) + } + } + + @Test + fun delayString() { + methods.forEach { + json(it) + .request(it, "/delay/a") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) + } + } + + @Test + fun delayDouble() { + methods.forEach { + json(it) + .request(it, "/delay/1.8") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) + } + } + + @Test + fun delayNegative() { + methods.forEach { + json(it) + .request(it, "/delay/-1") + .then() + .statusCode(400) + .body("body", equalTo("Invalid delay: -1"), *bodyMatchers(it)) + } + } + + @Test + fun delay0() { + methods.forEach { + json(it) + .request(it, "/delay/0") + .then() + .statusCode(200) + .body( + "body", + equalTo("Slept for 0 seconds."), + *bodyMatchers(it), + ) + } + } + + @Test + fun delay1() { + methods.forEach { + json(it) + .request(it, "/delay/1") + .then() + .statusCode(200) + .body( + "body", + equalTo("Slept for 1 seconds."), + *bodyMatchers(it), + ) + } + } + + @Test + @Disabled + fun delay10() { + methods.forEach { + json(it) + .request(it, "/delay/10") + .then() + .statusCode(200) + .body( + "body", + equalTo("Slept for 10 seconds."), + *bodyMatchers(it), + ) + } + } + + @Test + fun delay11() { + methods.forEach { + json(it) + .request(it, "/delay/11") + .then() + .statusCode(400) + .body( + "body", + equalTo("Invalid delay: 11"), + *bodyMatchers(it), + ) + } + } +} diff --git a/src/test/kotlin/com/testainers/LengthResourceTest.kt b/src/test/kotlin/com/testainers/LengthResourceTest.kt new file mode 100644 index 0000000..062fd1b --- /dev/null +++ b/src/test/kotlin/com/testainers/LengthResourceTest.kt @@ -0,0 +1,92 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.ContentType +import io.restassured.http.Method +import io.restassured.module.kotlin.extensions.* +import jakarta.ws.rs.core.MediaType +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.notNullValue +import org.junit.jupiter.api.Test + +/** + * @author Eduardo Folly + */ +@QuarkusTest +class LengthResourceTest { + val methods = listOf(Method.GET, Method.POST, Method.PUT, Method.DELETE) + + @Test + fun length0() { + methods.forEach { + When { + request(it, "/length/0") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid size: 0")) + } + } + } + + @Test + fun length5() { + methods.forEach { + When { + request(it, "/length/5") + } Then { + statusCode(200) + contentType(ContentType.TEXT) + header("Content-Length", "5") + body(equalTo("0".repeat(5))) + } + } + } + + @Test + fun length10() { + methods.forEach { + When { + request(it, "/length/10") + } Then { + statusCode(200) + contentType(ContentType.TEXT) + header("Content-Length", "10") + body(equalTo("0".repeat(10))) + } + } + } + + @Test + fun length1024() { + methods.forEach { + Given { + given() + accept(MediaType.APPLICATION_OCTET_STREAM) + } When { + request(it, "/length/1024") + } Then { + statusCode(200) + contentType(ContentType.BINARY) + header("Content-Length", "1024") + body(notNullValue()) + } + } + } + + @Test + fun length2049() { + methods.forEach { + Given { + given() + accept(MediaType.APPLICATION_OCTET_STREAM) + } When { + request(it, "/length/2049") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid size: 2049")) + } + } + } +} diff --git a/src/test/kotlin/com/testainers/MethodsResourceTest.kt b/src/test/kotlin/com/testainers/MethodsResourceTest.kt new file mode 100644 index 0000000..14411f1 --- /dev/null +++ b/src/test/kotlin/com/testainers/MethodsResourceTest.kt @@ -0,0 +1,70 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.Method +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +/** + * @author Eduardo Folly + */ +@QuarkusTest +class MethodsResourceTest : BaseResourceTest() { + @Test + fun methodGet() { + val method = Method.GET + json(method) + .request(method, "/methods") + .then() + .statusCode(200) + .body("", notNullValue(), *bodyMatchers(method)) + } + + @Test + fun methodHead() { + json(Method.HEAD) + .head("/methods") + .then() + .statusCode(200) + } + + @Test + fun methodPost() { + val method = Method.POST + json(method) + .request(method, "/methods") + .then() + .statusCode(200) + .body("body", equalTo(body), *bodyMatchers(method)) + } + + @Test + fun methodPut() { + val method = Method.PUT + json(method) + .request(method, "/methods") + .then() + .statusCode(200) + .body("body", equalTo(body), *bodyMatchers(method)) + } + + @Test + fun methodPatch() { + val method = Method.PATCH + json(method) + .request(method, "/methods") + .then() + .statusCode(200) + .body("body", equalTo(body), *bodyMatchers(method)) + } + + @Test + fun methodDelete() { + val method = Method.DELETE + json(method) + .request(method, "/methods") + .then() + .statusCode(200) + .body("body", equalTo(body), *bodyMatchers(method)) + } +} diff --git a/src/test/kotlin/com/testainers/RedirectResourceTest.kt b/src/test/kotlin/com/testainers/RedirectResourceTest.kt new file mode 100644 index 0000000..950b430 --- /dev/null +++ b/src/test/kotlin/com/testainers/RedirectResourceTest.kt @@ -0,0 +1,151 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.RestAssured +import io.restassured.config.LogConfig +import io.restassured.config.RedirectConfig +import io.restassured.http.ContentType +import io.restassured.http.Method +import io.restassured.module.kotlin.extensions.* +import jakarta.ws.rs.core.HttpHeaders +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +/** + * @author Eduardo Folly + */ +@QuarkusTest +class RedirectResourceTest { + val location = "https://testainers.com" + + val invalidSchema = "testainers.com" + + val methods = + listOf(Method.GET, Method.POST, Method.PUT, Method.PATCH, Method.DELETE) + + val config = + RestAssured + .config() + .logConfig( + LogConfig + .logConfig() + .enableLoggingOfRequestAndResponseIfValidationFails() + .enablePrettyPrinting(true), + ).redirect( + RedirectConfig.redirectConfig().followRedirects(false), + ) + + @Test + fun redirect302() { + methods.forEach { + Given { + config(config) + } When { + queryParam("url", location) + request(it, "/redirect") + } Then { + statusCode(302) + header(HttpHeaders.LOCATION, location) + header(HttpHeaders.CONTENT_LENGTH, "0") + } + } + } + + @Test + fun redirect303() { + methods.forEach { + Given { + config(config) + } When { + queryParam("url", location) + queryParam("code", 303) + request(it, "/redirect") + } Then { + statusCode(303) + header(HttpHeaders.LOCATION, location) + header(HttpHeaders.CONTENT_LENGTH, "0") + } + } + } + + @Test + fun redirect299() { + methods.forEach { + Given { + config(config) + } When { + queryParam("url", location) + queryParam("code", 299) + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid status code: 299")) + } + } + } + + @Test + fun redirect499() { + methods.forEach { + Given { + config(config) + } When { + queryParam("url", location) + queryParam("code", 499) + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid status code: 499")) + } + } + } + + @Test + fun redirectNoUrl() { + methods.forEach { + Given { + config(config) + } When { + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid URL Scheme: ")) + } + } + } + + @Test + fun redirectEmptyUrl() { + methods.forEach { + Given { + config(config) + } When { + queryParam("url", "") + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid URL Scheme: ")) + } + } + } + + @Test + fun redirectInvalidScheme() { + methods.forEach { + Given { + config(config) + } When { + queryParam("url", invalidSchema) + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid URL Scheme: $invalidSchema")) + } + } + } +} diff --git a/src/test/kotlin/com/testainers/StatusResourceTest.kt b/src/test/kotlin/com/testainers/StatusResourceTest.kt new file mode 100644 index 0000000..5696709 --- /dev/null +++ b/src/test/kotlin/com/testainers/StatusResourceTest.kt @@ -0,0 +1,188 @@ +package com.testainers + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.Method +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +/** + * @author Eduardo Folly + */ +@QuarkusTest +class StatusResourceTest : BaseResourceTest() { + @Test + fun statusString() { + methods.forEach { + json(it) + .request(it, "/status/a") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) + } + } + + @Test + fun statusDouble() { + methods.forEach { + json(it) + .request(it, "/status/1.8") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) + } + } + + @Test + fun statusNegative() { + methods.forEach { + json(it) + .request(it, "/status/-1") + .then() + .statusCode(500) + .body( + "body", + equalTo("Unknown status code: -1"), + *bodyMatchers(it), + ) + } + } + + @Test + fun status0() { + methods.forEach { + json(it) + .request(it, "/status/0") + .then() + .statusCode(500) + .body( + "body", + equalTo("Unknown status code: 0"), + *bodyMatchers(it), + ) + } + } + + @Test + fun status99() { + methods.forEach { + json(it) + .request(it, "/status/99") + .then() + .statusCode(500) + .body( + "body", + equalTo("Unknown status code: 99"), + *bodyMatchers(it), + ) + } + } + + @Test + fun status100() { + methods.forEach { + json(it) + .request(it, "/status/100") + .then() + .statusCode(500) + .body( + "body", + equalTo("Informational responses are not supported: 100"), + *bodyMatchers(it), + ) + } + } + + @Test + fun status199() { + methods.forEach { + json(it) + .request(it, "/status/199") + .then() + .statusCode(500) + .body( + "body", + equalTo("Informational responses are not supported: 199"), + *bodyMatchers(it), + ) + } + } + + @Test + fun status200() { + methods.forEach { + json(it) + .request(it, "/status/200") + .then() + .statusCode(200) + .body( + "body", + if (it == Method.GET) not(body) else equalTo(body), + *bodyMatchers(it), + ) + } + } + + @Test + fun status204() { + methods.forEach { + json(it) + .request(it, "/status/204") + .then() + .statusCode(204) + .body(equalTo("")) + } + } + + @Test + fun status205() { + methods.forEach { + json(it) + .request(it, "/status/205") + .then() + .statusCode(205) + .headers("content-length", "0") + .body(equalTo("")) + } + } + + @Test + fun status304() { + methods.forEach { + json(it) + .request(it, "/status/304") + .then() + .statusCode(304) + .body(equalTo("")) + } + } + + @Test + fun status599() { + methods.forEach { + json(it) + .request(it, "/status/599") + .then() + .statusCode(599) + .body( + "body", + if (it == Method.GET) not(body) else equalTo(body), + *bodyMatchers(it), + ) + } + } + + @Test + fun status600() { + methods.forEach { + json(it) + .request(it, "/status/600") + .then() + .statusCode(500) + .body( + "body", + equalTo("Unknown status code: 600"), + *bodyMatchers(it), + ) + } + } +} From ea00c14140233b0bef9a70887596b8f960c1d594 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Thu, 30 May 2024 19:23:48 -0300 Subject: [PATCH 8/9] Updating to ParameterizedTest instead use foreach. --- .../kotlin/com/testainers/RedirectResource.kt | 16 +- .../kotlin/com/testainers/BaseResourceTest.kt | 33 ++- .../com/testainers/BasicAuthResourceTest.kt | 146 +++++------ .../com/testainers/DelayResourceTest.kt | 153 ++++------- .../com/testainers/LengthResourceTest.kt | 134 +++++----- .../com/testainers/RedirectResourceTest.kt | 221 ++++++++-------- .../com/testainers/StatusResourceTest.kt | 244 ++++++------------ 7 files changed, 431 insertions(+), 516 deletions(-) diff --git a/src/main/kotlin/com/testainers/RedirectResource.kt b/src/main/kotlin/com/testainers/RedirectResource.kt index 2f768b5..e32e577 100644 --- a/src/main/kotlin/com/testainers/RedirectResource.kt +++ b/src/main/kotlin/com/testainers/RedirectResource.kt @@ -88,21 +88,29 @@ class RedirectResource { ): Response { val uri: URI + if (url.isBlank()) { + return Response + .status(500) + .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + .entity("URL is required") + .build() + } + try { uri = URI.create(url) } catch (e: Exception) { return Response .status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid URL: %s", url)) + .entity("Invalid URL: $url") .build() } - if (uri.scheme == null) { + if (uri.scheme?.startsWith("http") != true) { return Response .status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid URL Scheme: %s", url)) + .entity("Invalid URL Scheme: $url") .build() } @@ -110,7 +118,7 @@ class RedirectResource { return Response .status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) - .entity(String.format("Invalid status code: %d", code)) + .entity("Invalid status code: $code") .build() } diff --git a/src/test/kotlin/com/testainers/BaseResourceTest.kt b/src/test/kotlin/com/testainers/BaseResourceTest.kt index 6e78a0b..6bcbf2c 100644 --- a/src/test/kotlin/com/testainers/BaseResourceTest.kt +++ b/src/test/kotlin/com/testainers/BaseResourceTest.kt @@ -7,15 +7,42 @@ import io.restassured.http.Method import io.restassured.specification.RequestSpecification import org.hamcrest.Matcher import org.hamcrest.Matchers.* +import org.junit.jupiter.params.provider.Arguments /** * @author Eduardo Folly */ abstract class BaseResourceTest { - protected val methods = - listOf(Method.GET, Method.POST, Method.PUT, Method.DELETE) + companion object { + private val methods = + listOf(Method.GET, Method.POST, Method.PUT, Method.DELETE) - private val config: RestAssuredConfig = + @JvmStatic + fun notFoundStatus(): List = + argumentGenerator(listOf(null, "", " ", "a", "1.8")) + + @JvmStatic + fun onlyMethods(): List = methods.map { Arguments.of(it) } + + @JvmStatic + fun argumentGenerator(list: List): List { + val result = mutableListOf() + + methods.forEach { method -> + list.forEach { status -> + result.add(Arguments.of(method, status)) + } + } + + return result + } + } + +// @Deprecated("Not use") +// protected val methods = +// listOf(Method.GET, Method.POST, Method.PUT, Method.DELETE) + + protected val config: RestAssuredConfig = RestAssured .config() .logConfig( diff --git a/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt b/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt index 6d6ee33..e917c9a 100644 --- a/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt +++ b/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt @@ -4,92 +4,92 @@ import io.quarkus.test.junit.QuarkusTest import io.restassured.http.Method import jakarta.ws.rs.core.HttpHeaders import org.hamcrest.Matchers.* -import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource /** * @author Eduardo Folly */ @QuarkusTest class BasicAuthResourceTest : BaseResourceTest() { - val user = "test" - val pass = "test-pass0" + companion object : BaseResourceTest() { + const val USER = "test" + const val PASS = "test-pass0" + } - @Test - fun noHeader() { - methods.forEach { - json(it) - .request(it, "/basic-auth/$user/$pass") - .then() - .statusCode(401) - .body( - "body.body", - if (it == Method.GET) nullValue() else equalTo(body), - *bodyMatchers( - it, - mapOf( - "body.auth" to equalTo(false), - "body.user" to equalTo(user), - "body.pass" to equalTo(pass), - "body.message" to - equalTo( - "Authorization header not present.", - ), - ), + @ParameterizedTest + @MethodSource("onlyMethods") + fun noHeader(method: Method) { + json(method) + .request(method, "/basic-auth/$USER/$PASS") + .then() + .statusCode(401) + .body( + "body.body", + if (method == Method.GET) nullValue() else equalTo(body), + *bodyMatchers( + method, + mapOf( + "body.auth" to equalTo(false), + "body.user" to equalTo(USER), + "body.pass" to equalTo(PASS), + "body.message" to + equalTo( + "Authorization header not present.", + ), ), - ) - } + ), + ) } - @Test - fun emptyHeader() { - methods.forEach { - json(it) - .header(HttpHeaders.AUTHORIZATION, "") - .request(it, "/basic-auth/$user/$pass") - .then() - .statusCode(401) - .body( - "body.body", - if (it == Method.GET) nullValue() else equalTo(body), - *bodyMatchers( - it, - mapOf( - "body.auth" to equalTo(false), - "body.user" to equalTo(user), - "body.pass" to equalTo(pass), - "body.message" to - equalTo( - "Authorization header not present.", - ), - ), + @ParameterizedTest + @MethodSource("onlyMethods") + fun emptyHeader(method: Method) { + json(method) + .header(HttpHeaders.AUTHORIZATION, "") + .request(method, "/basic-auth/$USER/$PASS") + .then() + .statusCode(401) + .body( + "body.body", + if (method == Method.GET) nullValue() else equalTo(body), + *bodyMatchers( + method, + mapOf( + "body.auth" to equalTo(false), + "body.user" to equalTo(USER), + "body.pass" to equalTo(PASS), + "body.message" to + equalTo( + "Authorization header not present.", + ), ), - ) - } + ), + ) } - @Test - fun success() { - methods.forEach { - json(it) - .auth() - .preemptive() - .basic(user, pass) - .request(it, "/basic-auth/$user/$pass") - .then() - .statusCode(200) - .body( - "body.body", - if (it == Method.GET) nullValue() else equalTo(body), - *bodyMatchers( - it, - mapOf( - "body.auth" to equalTo(true), - "body.user" to equalTo(user), - "body.pass" to equalTo(pass), - "body.message" to equalTo("Success."), - ), + @ParameterizedTest + @MethodSource("onlyMethods") + fun success(method: Method) { + json(method) + .auth() + .preemptive() + .basic(USER, PASS) + .request(method, "/basic-auth/$USER/$PASS") + .then() + .statusCode(200) + .body( + "body.body", + if (method == Method.GET) nullValue() else equalTo(body), + *bodyMatchers( + method, + mapOf( + "body.auth" to equalTo(true), + "body.user" to equalTo(USER), + "body.pass" to equalTo(PASS), + "body.message" to equalTo("Success."), ), - ) - } + ), + ) } } diff --git a/src/test/kotlin/com/testainers/DelayResourceTest.kt b/src/test/kotlin/com/testainers/DelayResourceTest.kt index 16a2900..4f2eb02 100644 --- a/src/test/kotlin/com/testainers/DelayResourceTest.kt +++ b/src/test/kotlin/com/testainers/DelayResourceTest.kt @@ -1,117 +1,78 @@ package com.testainers import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.Method import org.hamcrest.Matchers.* -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.* /** * @author Eduardo Folly */ @QuarkusTest class DelayResourceTest : BaseResourceTest() { - @Test - fun delayEmpty() { - methods.forEach { - json(it) - .request(it, "/delay") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")) - } - } - - @Test - fun delayString() { - methods.forEach { - json(it) - .request(it, "/delay/a") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")) - } - } - - @Test - fun delayDouble() { - methods.forEach { - json(it) - .request(it, "/delay/1.8") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")) - } - } + companion object : BaseResourceTest() { + @JvmStatic + fun invalidDelay(): List = argumentGenerator(listOf(-1, 11)) - @Test - fun delayNegative() { - methods.forEach { - json(it) - .request(it, "/delay/-1") - .then() - .statusCode(400) - .body("body", equalTo("Invalid delay: -1"), *bodyMatchers(it)) - } + @JvmStatic + fun validDelay(): List = argumentGenerator(listOf(0, 1)) } - @Test - fun delay0() { - methods.forEach { - json(it) - .request(it, "/delay/0") - .then() - .statusCode(200) - .body( - "body", - equalTo("Slept for 0 seconds."), - *bodyMatchers(it), - ) - } + @ParameterizedTest + @MethodSource("onlyMethods") + fun delayEmpty(method: Method) { + json(method) + .request(method, "/delay") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) } - @Test - fun delay1() { - methods.forEach { - json(it) - .request(it, "/delay/1") - .then() - .statusCode(200) - .body( - "body", - equalTo("Slept for 1 seconds."), - *bodyMatchers(it), - ) - } + @ParameterizedTest + @MethodSource("notFoundStatus") + fun notFound( + method: Method, + delay: String?, + ) { + json(method) + .request(method, "/delay/$delay") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) } - @Test - @Disabled - fun delay10() { - methods.forEach { - json(it) - .request(it, "/delay/10") - .then() - .statusCode(200) - .body( - "body", - equalTo("Slept for 10 seconds."), - *bodyMatchers(it), - ) - } + @ParameterizedTest + @MethodSource("invalidDelay") + fun invalid( + method: Method, + delay: Int, + ) { + json(method) + .request(method, "/delay/$delay") + .then() + .statusCode(400) + .body( + "body", + equalTo("Invalid delay: $delay"), + *bodyMatchers(method), + ) } - @Test - fun delay11() { - methods.forEach { - json(it) - .request(it, "/delay/11") - .then() - .statusCode(400) - .body( - "body", - equalTo("Invalid delay: 11"), - *bodyMatchers(it), - ) - } + @ParameterizedTest + @MethodSource("validDelay") + fun valid( + it: Method, + delay: Int, + ) { + json(it) + .request(it, "/delay/$delay") + .then() + .statusCode(200) + .body( + "body", + equalTo("Slept for $delay seconds."), + *bodyMatchers(it), + ) } } diff --git a/src/test/kotlin/com/testainers/LengthResourceTest.kt b/src/test/kotlin/com/testainers/LengthResourceTest.kt index 062fd1b..5fa88c9 100644 --- a/src/test/kotlin/com/testainers/LengthResourceTest.kt +++ b/src/test/kotlin/com/testainers/LengthResourceTest.kt @@ -5,88 +5,90 @@ import io.restassured.http.ContentType import io.restassured.http.Method import io.restassured.module.kotlin.extensions.* import jakarta.ws.rs.core.MediaType -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.notNullValue -import org.junit.jupiter.api.Test +import org.hamcrest.Matchers.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource /** * @author Eduardo Folly */ @QuarkusTest -class LengthResourceTest { - val methods = listOf(Method.GET, Method.POST, Method.PUT, Method.DELETE) +class LengthResourceTest : BaseResourceTest() { + companion object : BaseResourceTest() { + @JvmStatic + fun invalidLength(): List = + argumentGenerator(listOf(-1, 0, 2049)) - @Test - fun length0() { - methods.forEach { - When { - request(it, "/length/0") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid size: 0")) - } - } + @JvmStatic + fun successTextLength(): List = + argumentGenerator(listOf(1, 10)) + + @JvmStatic + fun successOctetLength(): List = + argumentGenerator(listOf(512, 2048)) } - @Test - fun length5() { - methods.forEach { - When { - request(it, "/length/5") - } Then { - statusCode(200) - contentType(ContentType.TEXT) - header("Content-Length", "5") - body(equalTo("0".repeat(5))) - } - } + @ParameterizedTest + @MethodSource("notFoundStatus") + fun notFound( + method: Method, + length: String?, + ) { + json(method) + .request(method, "/length/$length") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) } - @Test - fun length10() { - methods.forEach { - When { - request(it, "/length/10") - } Then { - statusCode(200) - contentType(ContentType.TEXT) - header("Content-Length", "10") - body(equalTo("0".repeat(10))) - } + @ParameterizedTest + @MethodSource("invalidLength") + fun invalid( + method: Method, + length: Int, + ) { + When { + request(method, "/length/$length") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid size: $length")) } } - @Test - fun length1024() { - methods.forEach { - Given { - given() - accept(MediaType.APPLICATION_OCTET_STREAM) - } When { - request(it, "/length/1024") - } Then { - statusCode(200) - contentType(ContentType.BINARY) - header("Content-Length", "1024") - body(notNullValue()) - } + @ParameterizedTest + @MethodSource("successTextLength") + fun successText( + it: Method, + length: Int, + ) { + When { + request(it, "/length/$length") + } Then { + statusCode(200) + contentType(ContentType.TEXT) + header("Content-Length", "$length") + body(equalTo("0".repeat(length))) } } - @Test - fun length2049() { - methods.forEach { - Given { - given() - accept(MediaType.APPLICATION_OCTET_STREAM) - } When { - request(it, "/length/2049") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid size: 2049")) - } + @ParameterizedTest + @MethodSource("successOctetLength") + fun successOctet( + it: Method, + length: Int, + ) { + Given { + given() + accept(MediaType.APPLICATION_OCTET_STREAM) + } When { + request(it, "/length/$length") + } Then { + statusCode(200) + contentType(ContentType.BINARY) + header("Content-Length", "$length") + body(notNullValue()) } } } diff --git a/src/test/kotlin/com/testainers/RedirectResourceTest.kt b/src/test/kotlin/com/testainers/RedirectResourceTest.kt index 950b430..bb8bbd2 100644 --- a/src/test/kotlin/com/testainers/RedirectResourceTest.kt +++ b/src/test/kotlin/com/testainers/RedirectResourceTest.kt @@ -1,151 +1,148 @@ package com.testainers import io.quarkus.test.junit.QuarkusTest -import io.restassured.RestAssured -import io.restassured.config.LogConfig -import io.restassured.config.RedirectConfig import io.restassured.http.ContentType import io.restassured.http.Method import io.restassured.module.kotlin.extensions.* import jakarta.ws.rs.core.HttpHeaders import org.hamcrest.Matchers.* -import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource /** * @author Eduardo Folly */ @QuarkusTest -class RedirectResourceTest { - val location = "https://testainers.com" +class RedirectResourceTest : BaseResourceTest() { + companion object : BaseResourceTest() { + const val LOCATION = "https://testainers.com" - val invalidSchema = "testainers.com" + @JvmStatic + fun invalidRedirect(): List = + argumentGenerator(listOf(299, 400)) - val methods = - listOf(Method.GET, Method.POST, Method.PUT, Method.PATCH, Method.DELETE) + @JvmStatic + fun requiredUrl(): List = + argumentGenerator(listOf(null, "", " ")) - val config = - RestAssured - .config() - .logConfig( - LogConfig - .logConfig() - .enableLoggingOfRequestAndResponseIfValidationFails() - .enablePrettyPrinting(true), - ).redirect( - RedirectConfig.redirectConfig().followRedirects(false), - ) + @JvmStatic + fun invalidUrl(): List = + argumentGenerator(listOf("a{b}", "a:|b")) - @Test - fun redirect302() { - methods.forEach { - Given { - config(config) - } When { - queryParam("url", location) - request(it, "/redirect") - } Then { - statusCode(302) - header(HttpHeaders.LOCATION, location) - header(HttpHeaders.CONTENT_LENGTH, "0") - } - } - } + @JvmStatic + fun invalidScheme(): List = + argumentGenerator( + listOf( + "testainers.com", + "1.8", + "a@b", + "ftp://teste.com", + ), + ) - @Test - fun redirect303() { - methods.forEach { - Given { - config(config) - } When { - queryParam("url", location) - queryParam("code", 303) - request(it, "/redirect") - } Then { - statusCode(303) - header(HttpHeaders.LOCATION, location) - header(HttpHeaders.CONTENT_LENGTH, "0") - } - } + @JvmStatic + fun validRedirect(): List = + argumentGenerator(listOf(null, "", "302", "303")) } - @Test - fun redirect299() { - methods.forEach { - Given { - config(config) - } When { - queryParam("url", location) - queryParam("code", 299) - request(it, "/redirect") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid status code: 299")) - } + @ParameterizedTest + @MethodSource("invalidRedirect") + fun invalid( + it: Method, + code: Int, + ) { + Given { + config(config) + } When { + queryParam("url", LOCATION) + queryParam("code", code) + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid status code: $code")) } } - @Test - fun redirect499() { - methods.forEach { - Given { - config(config) - } When { - queryParam("url", location) - queryParam("code", 499) - request(it, "/redirect") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid status code: 499")) + @ParameterizedTest + @MethodSource("validRedirect") + fun valid( + it: Method, + code: String?, + ) { + Given { + config(config) + } When { + queryParam("url", LOCATION) + if (!code.isNullOrBlank()) { + queryParam("code", code) } + request(it, "/redirect") + } Then { + statusCode(code?.toIntOrNull() ?: 302) + header(HttpHeaders.LOCATION, LOCATION) + header(HttpHeaders.CONTENT_LENGTH, "0") } } - @Test - fun redirectNoUrl() { - methods.forEach { - Given { - config(config) - } When { - request(it, "/redirect") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid URL Scheme: ")) + @ParameterizedTest + @MethodSource("requiredUrl") + fun required( + it: Method, + url: String?, + ) { + Given { + config(config) + } When { + if (url != null) { + queryParam("url", url) } + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("URL is required")) } } - @Test - fun redirectEmptyUrl() { - methods.forEach { - Given { - config(config) - } When { - queryParam("url", "") - request(it, "/redirect") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid URL Scheme: ")) + @ParameterizedTest + @MethodSource("invalidUrl") + fun invalid( + it: Method, + url: String?, + ) { + Given { + config(config) + } When { + if (url != null) { + queryParam("url", url) } + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid URL: $url")) } } - @Test - fun redirectInvalidScheme() { - methods.forEach { - Given { - config(config) - } When { - queryParam("url", invalidSchema) - request(it, "/redirect") - } Then { - statusCode(500) - contentType(ContentType.TEXT) - body(equalTo("Invalid URL Scheme: $invalidSchema")) + @ParameterizedTest + @MethodSource("invalidScheme") + fun scheme( + it: Method, + url: String?, + ) { + Given { + config(config) + } When { + if (url != null) { + queryParam("url", url) } + request(it, "/redirect") + } Then { + statusCode(500) + contentType(ContentType.TEXT) + body(equalTo("Invalid URL Scheme: $url")) } } } diff --git a/src/test/kotlin/com/testainers/StatusResourceTest.kt b/src/test/kotlin/com/testainers/StatusResourceTest.kt index 5696709..47ead91 100644 --- a/src/test/kotlin/com/testainers/StatusResourceTest.kt +++ b/src/test/kotlin/com/testainers/StatusResourceTest.kt @@ -3,186 +3,106 @@ package com.testainers import io.quarkus.test.junit.QuarkusTest import io.restassured.http.Method import org.hamcrest.Matchers.* -import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource /** * @author Eduardo Folly */ @QuarkusTest class StatusResourceTest : BaseResourceTest() { - @Test - fun statusString() { - methods.forEach { - json(it) - .request(it, "/status/a") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")) - } - } - - @Test - fun statusDouble() { - methods.forEach { - json(it) - .request(it, "/status/1.8") - .then() - .statusCode(404) - .statusLine(containsStringIgnoringCase("Not Found")) - } - } + companion object : BaseResourceTest() { + @JvmStatic + fun unknownStatus(): List = + argumentGenerator(listOf(-1, 0, 1, 99, 600)) - @Test - fun statusNegative() { - methods.forEach { - json(it) - .request(it, "/status/-1") - .then() - .statusCode(500) - .body( - "body", - equalTo("Unknown status code: -1"), - *bodyMatchers(it), - ) - } - } - - @Test - fun status0() { - methods.forEach { - json(it) - .request(it, "/status/0") - .then() - .statusCode(500) - .body( - "body", - equalTo("Unknown status code: 0"), - *bodyMatchers(it), - ) - } - } + @JvmStatic + fun informationalStatus(): List = + argumentGenerator(listOf(100, 199)) - @Test - fun status99() { - methods.forEach { - json(it) - .request(it, "/status/99") - .then() - .statusCode(500) - .body( - "body", - equalTo("Unknown status code: 99"), - *bodyMatchers(it), - ) - } - } - - @Test - fun status100() { - methods.forEach { - json(it) - .request(it, "/status/100") - .then() - .statusCode(500) - .body( - "body", - equalTo("Informational responses are not supported: 100"), - *bodyMatchers(it), - ) - } - } - - @Test - fun status199() { - methods.forEach { - json(it) - .request(it, "/status/199") - .then() - .statusCode(500) - .body( - "body", - equalTo("Informational responses are not supported: 199"), - *bodyMatchers(it), - ) - } - } + @JvmStatic + fun validStatus(): List = argumentGenerator(listOf(200, 599)) - @Test - fun status200() { - methods.forEach { - json(it) - .request(it, "/status/200") - .then() - .statusCode(200) - .body( - "body", - if (it == Method.GET) not(body) else equalTo(body), - *bodyMatchers(it), - ) - } + @JvmStatic + fun emptyStatus(): List = + argumentGenerator(listOf(204, 205, 304)) } - @Test - fun status204() { - methods.forEach { - json(it) - .request(it, "/status/204") - .then() - .statusCode(204) - .body(equalTo("")) - } + @ParameterizedTest + @MethodSource("notFoundStatus") + fun notFound( + method: Method, + status: String?, + ) { + json(method) + .request(method, "/status/$status") + .then() + .statusCode(404) + .statusLine(containsStringIgnoringCase("Not Found")) } - @Test - fun status205() { - methods.forEach { - json(it) - .request(it, "/status/205") - .then() - .statusCode(205) - .headers("content-length", "0") - .body(equalTo("")) - } + @ParameterizedTest + @MethodSource("unknownStatus") + fun unknown( + method: Method, + status: Int, + ) { + json(method) + .request(method, "/status/$status") + .then() + .statusCode(500) + .body( + "body", + equalTo("Unknown status code: $status"), + *bodyMatchers(method), + ) } - @Test - fun status304() { - methods.forEach { - json(it) - .request(it, "/status/304") - .then() - .statusCode(304) - .body(equalTo("")) - } + @ParameterizedTest + @MethodSource("informationalStatus") + fun informational( + method: Method, + status: Int, + ) { + json(method) + .request(method, "/status/$status") + .then() + .statusCode(500) + .body( + "body", + equalTo("Informational responses are not supported: $status"), + *bodyMatchers(method), + ) } - @Test - fun status599() { - methods.forEach { - json(it) - .request(it, "/status/599") - .then() - .statusCode(599) - .body( - "body", - if (it == Method.GET) not(body) else equalTo(body), - *bodyMatchers(it), - ) - } + @ParameterizedTest + @MethodSource("validStatus") + fun valid( + method: Method, + status: Int, + ) { + json(method) + .request(method, "/status/$status") + .then() + .statusCode(status) + .body( + "body", + if (method == Method.GET) not(body) else equalTo(body), + *bodyMatchers(method), + ) } - @Test - fun status600() { - methods.forEach { - json(it) - .request(it, "/status/600") - .then() - .statusCode(500) - .body( - "body", - equalTo("Unknown status code: 600"), - *bodyMatchers(it), - ) - } + @ParameterizedTest + @MethodSource("emptyStatus") + fun empty( + method: Method, + status: Int, + ) { + json(method) + .request(method, "/status/$status") + .then() + .statusCode(status) + .body(equalTo("")) } } From 5c8b8c12d2aa789a30b1bac994307980c10cd9fa Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Thu, 30 May 2024 19:27:01 -0300 Subject: [PATCH 9/9] Version bump 0.1.0. --- .github/workflows/main.yml | 2 +- build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a234cb1..d5ecc42 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ jobs: run: | echo "TAGS=latest" >> $GITHUB_ENV SUFFIX="" - VERSION=$(grep 'version' build.gradle | cut -f 2 -d "'")$SUFFIX + VERSION=$(grep 'version =' build.gradle.kts | cut -f 2 -d '"')$SUFFIX echo "VERSION=$VERSION" >> $GITHUB_ENV REPO=$(echo $GITHUB_REPOSITORY | cut -f 2 -d "/") echo "REPO=$REPO" >> $GITHUB_ENV diff --git a/build.gradle.kts b/build.gradle.kts index 9259c57..885fada 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { } group = "com.testainers" -version = "0.0.8" +version = "0.1.0" java { sourceCompatibility = JavaVersion.VERSION_21