From b3afc610c593024c013185ba93a3a3e7988bd9ca Mon Sep 17 00:00:00 2001 From: Edgar Asatryan Date: Thu, 14 Apr 2022 22:28:02 +0400 Subject: [PATCH] fix: NPE when no `Content-Encoding` header present. --- ...io.http.ext.publish-conventions.gradle.kts | 132 +++++------ ...io.http.ext.quality-conventions.gradle.kts | 16 -- ...stdio.http.ext.test-conventions.gradle.kts | 208 +++++++++--------- .../nstdio/http/ext/ReadmeUpdateTask.kt | 52 ++--- .../http/ext/DecompressingBodyHandler.java | 3 +- .../ext/DecompressingSubscriberSpiTest.kt | 63 ++++-- .../spi/Brotli4JCompressionFactorySpiTest.kt | 33 ++- .../spi/BrotliOrgCompressionFactorySpiTest.kt | 34 +-- src/spiTest/resources/__files/br | Bin 0 -> 197 bytes src/spiTest/resources/__files/deflate | 2 + src/spiTest/resources/__files/gzip | Bin 0 -> 235 bytes .../io/github/nstdio/http/ext/Compression.kt | 42 ++-- ...DecompressingBodyHandlerIntegrationTest.kt | 79 +++++-- 13 files changed, 354 insertions(+), 310 deletions(-) create mode 100644 src/spiTest/resources/__files/br create mode 100644 src/spiTest/resources/__files/deflate create mode 100644 src/spiTest/resources/__files/gzip diff --git a/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.publish-conventions.gradle.kts index 7fc5f29..8a349fc 100644 --- a/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.publish-conventions.gradle.kts @@ -19,92 +19,92 @@ import se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask import se.bjurr.gitchangelog.plugin.gradle.HelperParam plugins { - signing - `maven-publish` + signing + `maven-publish` - id("io.github.gradle-nexus.publish-plugin") - id("se.bjurr.gitchangelog.git-changelog-gradle-plugin") + id("io.github.gradle-nexus.publish-plugin") + id("se.bjurr.gitchangelog.git-changelog-gradle-plugin") } publishing { - publications { - create("mavenJava") { - from(components["java"]) - - pom { - name.set("JDK Http Client Extensions") - description.set("The set of useful extensions for JDK HttpClient.") - url.set("https://github.com/nstdio/http-client-ext") - - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - - developers { - developer { - id.set("nstdio") - name.set("Edgar Asatryan") - email.set("nstdio@gmail.com") - } - } - - scm { - connection.set("scm:git:git@github.com:nstdio/http-client-ext.git") - developerConnection.set("scm:git:git@github.com:nstdio/http-client-ext.git") - url.set("https://github.com/nstdio/http-client-ext") - } - } + publications { + create("mavenJava") { + from(components["java"]) + + pom { + name.set("JDK Http Client Extensions") + description.set("The set of useful extensions for JDK HttpClient.") + url.set("https://github.com/nstdio/http-client-ext") + + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } } + + developers { + developer { + id.set("nstdio") + name.set("Edgar Asatryan") + email.set("nstdio@gmail.com") + } + } + + scm { + connection.set("scm:git:git@github.com:nstdio/http-client-ext.git") + developerConnection.set("scm:git:git@github.com:nstdio/http-client-ext.git") + url.set("https://github.com/nstdio/http-client-ext") + } + } } + } } nexusPublishing { - repositories { - sonatype { - val baseUri = uri("https://s01.oss.sonatype.org") - nexusUrl.set(baseUri.resolve("/service/local/")) - snapshotRepositoryUrl.set(baseUri.resolve("/content/repositories/snapshots/")) - } + repositories { + sonatype { + val baseUri = uri("https://s01.oss.sonatype.org") + nexusUrl.set(baseUri.resolve("/service/local/")) + snapshotRepositoryUrl.set(baseUri.resolve("/content/repositories/snapshots/")) } + } } signing { - isRequired = (version as String).endsWith("SNAPSHOT") + isRequired = (version as String).endsWith("SNAPSHOT") - val signingKey = findProperty("signingKey") as String? - val signingPassword = findProperty("signingPassword") as String? - useInMemoryPgpKeys(signingKey, signingPassword) + val signingKey = findProperty("signingKey") as String? + val signingPassword = findProperty("signingPassword") as String? + useInMemoryPgpKeys(signingKey, signingPassword) - sign(publishing.publications["mavenJava"]) + sign(publishing.publications["mavenJava"]) } tasks.withType().configureEach { - isPreserveFileTimestamps = false - isReproducibleFileOrder = true + isPreserveFileTimestamps = false + isReproducibleFileOrder = true } tasks.create("changelog", GitChangelogTask::class) { - fromRepo = project.rootDir.path - file = File("CHANGELOG.md") - handlebarsHelpers = listOf( - HelperParam("shortHash") { _: Any, options -> - return@HelperParam options.get("hash").substring(0, 7) - }, - HelperParam("compare") { _, options -> - val tagNames = options.get>("tags").map { it.name } - val name = options.get("name") - val prevTagIdx = tagNames.indexOf(name) + 1 - val compare = name.takeIf { it != "Unreleased" } ?: "HEAD" - - return@HelperParam if (prevTagIdx < tagNames.size) "compare/${tagNames[prevTagIdx]}...$compare" - else "releases/tag/$name" - } - ) - - doFirst { - templateContent = file("changelog.mustache").readText() + fromRepo = project.rootDir.path + file = File("CHANGELOG.md") + handlebarsHelpers = listOf( + HelperParam("shortHash") { _: Any, options -> + return@HelperParam options.get("hash").substring(0, 7) + }, + HelperParam("compare") { _, options -> + val tagNames = options.get>("tags").map { it.name } + val name = options.get("name") + val prevTagIdx = tagNames.indexOf(name) + 1 + val compare = name.takeIf { it != "Unreleased" } ?: "HEAD" + + return@HelperParam if (prevTagIdx < tagNames.size) "compare/${tagNames[prevTagIdx]}...$compare" + else "releases/tag/$name" } + ) + + doFirst { + templateContent = file("changelog.mustache").readText() + } } diff --git a/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.quality-conventions.gradle.kts b/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.quality-conventions.gradle.kts index 5147fdb..413a910 100644 --- a/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.quality-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.quality-conventions.gradle.kts @@ -17,22 +17,6 @@ import com.github.spotbugs.snom.Effort import com.github.spotbugs.snom.SpotBugsTask -/* - * Copyright (C) 2022 Edgar Asatryan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - plugins { jacoco id("com.github.spotbugs") diff --git a/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.test-conventions.gradle.kts b/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.test-conventions.gradle.kts index d1f0dfa..63d26cf 100644 --- a/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.test-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/io.github.nstdio.http.ext.test-conventions.gradle.kts @@ -28,46 +28,46 @@ import kotlin.Suppress import kotlin.to plugins { - `java-library` - idea - id("de.jjohannes.extra-java-module-info") + `java-library` + idea + id("de.jjohannes.extra-java-module-info") } val isCI = System.getenv("CI").toBoolean() java { - sourceSets { - create("spiTest") { - val output = sourceSets.main.get().output - compileClasspath += output - runtimeClasspath += output - } + sourceSets { + create("spiTest") { + val output = sourceSets.main.get().output + compileClasspath += output + runtimeClasspath += output } + } } val sourceSetsSpiTest by sourceSets.named("spiTest") val spiTestImplementation by configurations idea.module { - testSourceDirs.addAll(sourceSetsSpiTest.allSource.srcDirs) + testSourceDirs.addAll(sourceSetsSpiTest.allSource.srcDirs) } mapOf( - "spiTestImplementation" to "testImplementation", - "spiTestRuntimeOnly" to "testRuntimeOnly", + "spiTestImplementation" to "testImplementation", + "spiTestRuntimeOnly" to "testRuntimeOnly", ).forEach { (t, u) -> - configurations.getByName(t) { extendsFrom(configurations.getByName(u)) } + configurations.getByName(t) { extendsFrom(configurations.getByName(u)) } } tasks.withType { - useJUnitPlatform() - reports { - html.required.set(!isCI) - } - testLogging { - events("skipped", "failed") - exceptionFormat = TestExceptionFormat.FULL - } + useJUnitPlatform() + reports { + html.required.set(!isCI) + } + testLogging { + events("skipped", "failed") + exceptionFormat = TestExceptionFormat.FULL + } } val junitVersion = "5.8.2" @@ -82,126 +82,126 @@ val brotliOrgVersion = "0.1.2" val gsonVersion = "2.9.0" val jsonLibs = mapOf( - "jackson" to "com.fasterxml.jackson.core", - "gson" to "gson-$gsonVersion" + "jackson" to "com.fasterxml.jackson.core", + "gson" to "gson-$gsonVersion" ) val spiDeps = listOf( - "org.brotli:dec:$brotliOrgVersion", - "com.aayushatharva.brotli4j:brotli4j:$brotli4JVersion", - "com.google.code.gson:gson:$gsonVersion", - "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" + "org.brotli:dec:$brotliOrgVersion", + "com.aayushatharva.brotli4j:brotli4j:$brotli4JVersion", + "com.google.code.gson:gson:$gsonVersion", + "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" ) dependencies { - spiDeps.forEach { compileOnly(it) } + spiDeps.forEach { compileOnly(it) } - /** AssertJ & Friends */ - testImplementation("org.assertj:assertj-core:$assertJVersion") - testImplementation("io.kotest:kotest-assertions-core:$kotestAssertionsVersion") - testImplementation("io.kotest:kotest-property:$kotestAssertionsVersion") - testImplementation("com.jayway.jsonpath:json-path-assert:$jsonPathAssertVersion") + /** AssertJ & Friends */ + testImplementation("org.assertj:assertj-core:$assertJVersion") + testImplementation("io.kotest:kotest-assertions-core:$kotestAssertionsVersion") + testImplementation("io.kotest:kotest-property:$kotestAssertionsVersion") + testImplementation("io.kotest:kotest-assertions-json:$kotestAssertionsVersion") - /** Jupiter */ - testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") - testImplementation("org.mockito:mockito-core:$mockitoVersion") - testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion") + /** Jupiter */ + testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") + testImplementation("org.mockito:mockito-core:$mockitoVersion") + testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion") - testImplementation("org.slf4j:slf4j-simple:$slf4jVersion") + testImplementation("org.slf4j:slf4j-simple:$slf4jVersion") - testImplementation("org.awaitility:awaitility:4.2.0") + testImplementation("org.awaitility:awaitility:4.2.0") - testImplementation("nl.jqno.equalsverifier:equalsverifier:3.10") - testImplementation("com.github.tomakehurst:wiremock-jre8:2.33.1") - testImplementation("com.tngtech.archunit:archunit-junit5:0.23.1") + testImplementation("nl.jqno.equalsverifier:equalsverifier:3.10") + testImplementation("com.github.tomakehurst:wiremock-jre8:2.33.1") + testImplementation("com.tngtech.archunit:archunit-junit5:0.23.1") - spiDeps.forEach { spiTestImplementation(it) } - spiTestImplementation("com.aayushatharva.brotli4j:native-${getArch()}:$brotli4JVersion") + spiDeps.forEach { spiTestImplementation(it) } + spiTestImplementation("com.aayushatharva.brotli4j:native-${getArch()}:$brotli4JVersion") } Generator.subset(jsonLibs.keys) - .simple() - .stream() - .forEach { it -> - val postFix = it.joinToString("And") { it.capitalized() } - val taskName = if (postFix.isEmpty()) "spiTest" else "spiTestWithout${postFix}" - tasks.create(taskName) { - description = "Run SPI tests" - group = "verification" - testClassesDirs = sourceSetsSpiTest.output.classesDirs - classpath = sourceSetsSpiTest.runtimeClasspath - - doFirst { - val toExclude = classpath.filter { file -> - it?.any { file.absolutePath.contains(it) } ?: false - } - if (!toExclude.isEmpty) { - logger.info("Excluding jars from classpath: ") - toExclude.forEach { - logger.info(" - ${it.name}") - } - } - classpath -= toExclude - } + .simple() + .stream() + .forEach { it -> + val postFix = it.joinToString("And") { it.capitalized() } + val taskName = if (postFix.isEmpty()) "spiTest" else "spiTestWithout${postFix}" + tasks.create(taskName) { + description = "Run SPI tests" + group = "verification" + testClassesDirs = sourceSetsSpiTest.output.classesDirs + classpath = sourceSetsSpiTest.runtimeClasspath + + doFirst { + val toExclude = classpath.filter { file -> + it?.any { file.absolutePath.contains(it) } ?: false } + if (!toExclude.isEmpty) { + logger.info("Excluding jars from classpath: ") + toExclude.forEach { + logger.info(" - ${it.name}") + } + } + classpath -= toExclude + } } + } tasks.create("spiMatrixTest") { - description = "The aggregator task for all tests" - group = "verification" - dependsOn(tasks.filter { it.name.startsWith("spiTest") }) + description = "The aggregator task for all tests" + group = "verification" + dependsOn(tasks.filter { it.name.startsWith("spiTest") }) } tasks.withType { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } } tasks.check { - dependsOn("spiMatrixTest") + dependsOn("spiMatrixTest") } tasks.build { - dependsOn("spiMatrixTest") + dependsOn("spiMatrixTest") } configurations.names - .filter { !setOf("compileClasspath", "runtimeClasspath").contains(it) } - .map { configurations.getByName(it) } - .forEach { - configure(listOf(it)) { - attributes { - @Suppress("UNCHECKED_CAST") - val forName = Class.forName("java.lang.Boolean") as Class - val value: Boolean = Boolean.valueOf("false") as Boolean - - attribute(Attribute.of("javaModule", forName), value) - } - } + .filter { !setOf("compileClasspath", "runtimeClasspath").contains(it) } + .map { configurations.getByName(it) } + .forEach { + configure(listOf(it)) { + attributes { + @Suppress("UNCHECKED_CAST") + val forName = Class.forName("java.lang.Boolean") as Class + val value: Boolean = Boolean.valueOf("false") as Boolean + + attribute(Attribute.of("javaModule", forName), value) + } } + } extraJavaModuleInfo { - module("brotli4j-${brotli4JVersion}.jar", "com.aayushatharva.brotli4j", brotli4JVersion) { - exports("com.aayushatharva.brotli4j") - exports("com.aayushatharva.brotli4j.common") - exports("com.aayushatharva.brotli4j.decoder") - exports("com.aayushatharva.brotli4j.encoder") - } - - module("dec-${brotliOrgVersion}.jar", "org.brotli.dec", brotliOrgVersion) { - exports("org.brotli.dec") - } + module("brotli4j-${brotli4JVersion}.jar", "com.aayushatharva.brotli4j", brotli4JVersion) { + exports("com.aayushatharva.brotli4j") + exports("com.aayushatharva.brotli4j.common") + exports("com.aayushatharva.brotli4j.decoder") + exports("com.aayushatharva.brotli4j.encoder") + } + + module("dec-${brotliOrgVersion}.jar", "org.brotli.dec", brotliOrgVersion) { + exports("org.brotli.dec") + } } fun getArch(): String { - val operatingSystem = DefaultNativePlatform.getCurrentOperatingSystem() + val operatingSystem = DefaultNativePlatform.getCurrentOperatingSystem() - if (operatingSystem.isWindows) return "windows-x86_64" - else if (operatingSystem.isMacOsX) return "osx-x86_64" - else if (operatingSystem.isLinux) - return if (DefaultNativePlatform.getCurrentArchitecture().isArm) "linux-aarch64" - else "linux-x86_64" + if (operatingSystem.isWindows) return "windows-x86_64" + else if (operatingSystem.isMacOsX) return "osx-x86_64" + else if (operatingSystem.isLinux) + return if (DefaultNativePlatform.getCurrentArchitecture().isArm) "linux-aarch64" + else "linux-x86_64" - return "" + return "" } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/io/github/nstdio/http/ext/ReadmeUpdateTask.kt b/buildSrc/src/main/kotlin/io/github/nstdio/http/ext/ReadmeUpdateTask.kt index 9f9bf4c..a2e203f 100644 --- a/buildSrc/src/main/kotlin/io/github/nstdio/http/ext/ReadmeUpdateTask.kt +++ b/buildSrc/src/main/kotlin/io/github/nstdio/http/ext/ReadmeUpdateTask.kt @@ -21,40 +21,40 @@ import org.gradle.api.tasks.TaskAction import java.util.regex.Pattern abstract class ReadmeUpdateTask : DefaultTask() { - @TaskAction - fun update() { + @TaskAction + fun update() { - val v = project.version as String + val v = project.version as String - val file = project.file("README.md") - val text = StringBuilder(file.readText()) + val file = project.file("README.md") + val text = StringBuilder(file.readText()) - replaceMaven(text, v) - replaceGradle(text, v) + replaceMaven(text, v) + replaceGradle(text, v) - file.writeText(text.toString()) - } + file.writeText(text.toString()) + } - private fun replaceMaven(text: StringBuilder, version: String) { - val pattern = - "\\s+io\\.github\\.nstdio\\s+http-client-ext\\s+(.+)\\s+" - val mvnPattern = Pattern.compile(pattern, Pattern.MULTILINE) - replacePattern(mvnPattern, text, version) - } + private fun replaceMaven(text: StringBuilder, version: String) { + val pattern = + "\\s+io\\.github\\.nstdio\\s+http-client-ext\\s+(.+)\\s+" + val mvnPattern = Pattern.compile(pattern, Pattern.MULTILINE) + replacePattern(mvnPattern, text, version) + } - private fun replaceGradle(text: StringBuilder, version: String) { - val gradlePattern = Pattern.compile("implementation 'io\\.github\\.nstdio:http-client-ext:(.+)'") - replacePattern(gradlePattern, text, version) - } + private fun replaceGradle(text: StringBuilder, version: String) { + val gradlePattern = Pattern.compile("implementation 'io\\.github\\.nstdio:http-client-ext:(.+)'") + replacePattern(gradlePattern, text, version) + } - private fun replacePattern(pattern: Pattern, text: StringBuilder, version: String) { - val matcher = pattern.matcher(text) - if (matcher.find()) { - val start = matcher.start(1) - val end = matcher.end(1) + private fun replacePattern(pattern: Pattern, text: StringBuilder, version: String) { + val matcher = pattern.matcher(text) + if (matcher.find()) { + val start = matcher.start(1) + val end = matcher.end(1) - text.replace(start, end, version) - } + text.replace(start, end, version) } + } } diff --git a/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java b/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java index d3a917c..27d919a 100644 --- a/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java +++ b/src/main/java/io/github/nstdio/http/ext/DecompressingBodyHandler.java @@ -33,6 +33,7 @@ import java.util.function.UnaryOperator; import static io.github.nstdio.http.ext.Headers.HEADER_CONTENT_ENCODING; +import static java.net.http.HttpResponse.BodyHandlers.ofInputStream; import static java.util.stream.Collectors.toMap; class DecompressingBodyHandler implements BodyHandler { @@ -57,7 +58,7 @@ private DecompressingBodyHandler(BodyHandler original, Options options, boole } static DecompressingBodyHandler ofDirect(Options options) { - return new DecompressingBodyHandler<>(null, options, true); + return new DecompressingBodyHandler<>(ofInputStream(), options, true); } private UnaryOperator chain(UnaryOperator u1, UnaryOperator u2) { diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/DecompressingSubscriberSpiTest.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/DecompressingSubscriberSpiTest.kt index e5ad61b..2a64586 100644 --- a/src/spiTest/kotlin/io/github/nstdio/http/ext/DecompressingSubscriberSpiTest.kt +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/DecompressingSubscriberSpiTest.kt @@ -15,35 +15,60 @@ */ package io.github.nstdio.http.ext -import com.jayway.jsonpath.matchers.JsonPathMatchers.isJson +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.ok +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.junit5.WireMockExtension +import io.github.nstdio.http.ext.jupiter.EnabledIfOnClasspath +import io.kotest.assertions.json.shouldBeValidJson +import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -import java.io.IOException import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse.BodyHandlers.ofString +@EnabledIfOnClasspath(JACKSON) // required by WireMock internal class DecompressingSubscriberSpiTest { - private val client = HttpClient.newHttpClient() + private val client = HttpClient.newHttpClient() - @ParameterizedTest - @ValueSource(strings = ["brotli", "gzip", "deflate"]) - @Throws( - IOException::class, InterruptedException::class + @ParameterizedTest + @ValueSource(strings = ["br", "gzip", "deflate"]) + fun shouldDecompress(compressionType: String) { + //given + val uri = URI.create(wm.baseUrl()).resolve("/data") + stubFor( + get("/data").willReturn( + ok() + .withHeader("Content-Encoding", compressionType) + .withBodyFile(compressionType) + ) ) - fun shouldDecompress(compressionType: String) { - //given - val uri = URI.create("https://httpbin.org/").resolve(compressionType) - //when - val response = client.send( - HttpRequest.newBuilder(uri).build(), - BodyHandlers.ofDecompressing(ofString()) - ) - val body = response.body() + //when + val response = client.send( + HttpRequest.newBuilder(uri).build(), + BodyHandlers.ofDecompressing(ofString()) + ) + + //then + response.body().shouldBeValidJson() + } - //then - isJson().matches(body) - } + companion object { + @RegisterExtension + @JvmStatic + var wm = WireMockExtension.newInstance() + .configureStaticDsl(true) + .failOnUnmatchedRequests(true) + .options( + WireMockConfiguration.wireMockConfig() + .gzipDisabled(true) + .usingFilesUnderDirectory("src/spiTest/resources") + .dynamicPort() + ) + .build() + } } \ No newline at end of file diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/Brotli4JCompressionFactorySpiTest.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/Brotli4JCompressionFactorySpiTest.kt index 95ad19e..798ca53 100644 --- a/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/Brotli4JCompressionFactorySpiTest.kt +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/Brotli4JCompressionFactorySpiTest.kt @@ -15,29 +15,26 @@ */ package io.github.nstdio.http.ext.spi -import com.jayway.jsonpath.matchers.JsonPathMatchers.isJson +import io.kotest.assertions.json.shouldBeValidJson +import io.kotest.matchers.collections.shouldContainExactly import org.apache.commons.io.IOUtils -import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import java.io.IOException -import java.net.URI import java.nio.charset.StandardCharsets internal class Brotli4JCompressionFactorySpiTest { - @Test - @Throws(IOException::class) - fun shouldDecompress() { - //given - val inputStream = URI.create("https://httpbin.org/brotli").toURL().openStream() - val factory = Brotli4JCompressionFactory() + @Test + fun shouldDecompress() { + //given + val inputStream = Brotli4JCompressionFactorySpiTest::class.java.getResource("/__files/br")?.openStream() + val factory = Brotli4JCompressionFactory() - //when - val supported = factory.supported() - val actual = factory.decompressing(inputStream, "br") - val actualAsString = IOUtils.toString(actual, StandardCharsets.UTF_8) + //when + val supported = factory.supported() + val actual = factory.decompressing(inputStream, "br") + val actualAsString = IOUtils.toString(actual, StandardCharsets.UTF_8) - //then - isJson().matches(actualAsString) - assertThat(supported).containsExactly("br") - } + //then + actualAsString.shouldBeValidJson() + supported.shouldContainExactly("br") + } } \ No newline at end of file diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/BrotliOrgCompressionFactorySpiTest.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/BrotliOrgCompressionFactorySpiTest.kt index 1c4577a..ecf80de 100644 --- a/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/BrotliOrgCompressionFactorySpiTest.kt +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/spi/BrotliOrgCompressionFactorySpiTest.kt @@ -15,26 +15,26 @@ */ package io.github.nstdio.http.ext.spi -import com.jayway.jsonpath.matchers.JsonPathMatchers.isJson -import org.apache.commons.io.IOUtils.toString +import io.kotest.assertions.json.shouldBeValidJson +import io.kotest.matchers.collections.shouldContainExactly +import org.apache.commons.io.IOUtils import org.junit.jupiter.api.Test -import java.io.IOException -import java.net.URI -import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.charset.StandardCharsets internal class BrotliOrgCompressionFactorySpiTest { - @Test - @Throws(IOException::class) - fun shouldDecompress() { - //given - val inputStream = URI.create("https://httpbin.org/brotli").toURL().openStream() - val factory = BrotliOrgCompressionFactory() + @Test + fun shouldDecompress() { + //given + val inputStream = Brotli4JCompressionFactorySpiTest::class.java.getResource("/__files/br")?.openStream() + val factory = BrotliOrgCompressionFactory() - //when - val actual = factory.decompressing(inputStream, "br") - val actualAsString = toString(actual, UTF_8) + //when + val supported = factory.supported() + val actual = factory.decompressing(inputStream, "br") + val actualAsString = IOUtils.toString(actual, StandardCharsets.UTF_8) - //then - isJson().matches(actualAsString) - } + //then + actualAsString.shouldBeValidJson() + supported.shouldContainExactly("br") + } } \ No newline at end of file diff --git a/src/spiTest/resources/__files/br b/src/spiTest/resources/__files/br new file mode 100644 index 0000000000000000000000000000000000000000..aa7830fd669b1f0dd9fe5566baf19f9d01110e55 GIT binary patch literal 197 zcmV;$06PC0BLM&$2X-v@MlwiYFugXo&D0lFfzG>DT+*K)B7LR6VtLzy+??C|-V~8f zf*@*=oCT?|dCfz-$;p&9k9hu3d2aVSfO{<1v}eCSgfjYy$rlB3lsym)YY)~9ZTCLz zO^3n6kx@yRB?F0S`F7FT-}hb)tKYvctTf(=svyQ1E^TGC;>rstMd{0?uoB=6Qj-vXQ%XMV=XT|&G?IGN6g9I9NseC= literal 0 HcmV?d00001 diff --git a/src/spiTest/resources/__files/deflate b/src/spiTest/resources/__files/deflate new file mode 100644 index 0000000..3c4cec1 --- /dev/null +++ b/src/spiTest/resources/__files/deflate @@ -0,0 +1,2 @@ +x=N0E +K;~6mE팝&NU'Ν;G3 !܀= ޢ>!Gقj L;610߱hcQ+nN{*($^ 8n>m@jÒxQ_2owR?9e<'W㞓(7H-+^kl1D\!~Y~|8\؇u5LE`sL \ No newline at end of file diff --git a/src/spiTest/resources/__files/gzip b/src/spiTest/resources/__files/gzip new file mode 100644 index 0000000000000000000000000000000000000000..320d867887929795082c4d9fcc6da16c58457b1b GIT binary patch literal 235 zcmVaSPs1<_#qa$TQI4odNxEbmXonsUe~xHKT(i`qL9_{} zL1@#yJ81_y`T6vA#yo^+f%J%R??)bzyUboDNUU`*E{P}eZ7L!7 zH5aQ(1ie@dX*$6A;YNHisK{MfWer^El l!?JvTw>z_GNZ$Ez_Ubj4O7f=8rKZKH_yv;zV(cXW006dNa#sKV literal 0 HcmV?d00001 diff --git a/src/test/kotlin/io/github/nstdio/http/ext/Compression.kt b/src/test/kotlin/io/github/nstdio/http/ext/Compression.kt index fd33f9d..a4eca13 100644 --- a/src/test/kotlin/io/github/nstdio/http/ext/Compression.kt +++ b/src/test/kotlin/io/github/nstdio/http/ext/Compression.kt @@ -25,31 +25,27 @@ import java.util.zip.DeflaterOutputStream import java.util.zip.GZIPOutputStream internal object Compression { - fun gzip(input: String): ByteArray { - return compress(input) { out: OutputStream -> - try { - return@compress GZIPOutputStream(out, true) - } catch (e: IOException) { - throw UncheckedIOException(e) - } - } + fun gzip(input: String): ByteArray { + return compress(input) { out: OutputStream -> + try { + return@compress GZIPOutputStream(out, true) + } catch (e: IOException) { + throw UncheckedIOException(e) + } } + } - fun deflate(input: String): ByteArray { - return compress(input) { out: OutputStream -> DeflaterOutputStream(out, true) } - } + fun deflate(input: String): ByteArray { + return compress(input) { DeflaterOutputStream(it, true) } + } - private fun compress(input: String, compressorCreator: Function): ByteArray { - try { - ByteArrayOutputStream().use { out -> - compressorCreator.apply(out).use { compressor -> - compressor.write(input.toByteArray(StandardCharsets.UTF_8)) - compressor.finish() - return out.toByteArray() - } - } - } catch (e: IOException) { - throw UncheckedIOException(e) - } + private fun compress(input: String, compressorCreator: Function): ByteArray { + ByteArrayOutputStream().use { out -> + compressorCreator.apply(out).use { compressor -> + compressor.write(input.toByteArray(StandardCharsets.UTF_8)) + compressor.finish() + return out.toByteArray() + } } + } } \ No newline at end of file diff --git a/src/test/kotlin/io/github/nstdio/http/ext/InputStreamDecompressingBodyHandlerIntegrationTest.kt b/src/test/kotlin/io/github/nstdio/http/ext/InputStreamDecompressingBodyHandlerIntegrationTest.kt index d50cfe3..0b55d2e 100644 --- a/src/test/kotlin/io/github/nstdio/http/ext/InputStreamDecompressingBodyHandlerIntegrationTest.kt +++ b/src/test/kotlin/io/github/nstdio/http/ext/InputStreamDecompressingBodyHandlerIntegrationTest.kt @@ -13,35 +13,74 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("ArrayInDataClass") + package io.github.nstdio.http.ext -import com.jayway.jsonpath.matchers.JsonPathMatchers.isJson +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.ok +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.junit5.WireMockExtension +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.map +import io.kotest.property.arbitrary.next +import io.kotest.property.arbitrary.string import org.apache.commons.io.IOUtils -import org.hamcrest.MatcherAssert.assertThat +import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource +import org.junit.jupiter.params.provider.MethodSource import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.nio.charset.StandardCharsets internal class InputStreamDecompressingBodyHandlerIntegrationTest { - private val httpClient = HttpClient.newHttpClient() - private val baseUri = URI.create("https://httpbin.org/") - - @ParameterizedTest - @ValueSource(strings = ["gzip", "deflate"]) - @Throws(Exception::class) - fun shouldCreate(compression: String) { - //given - val request = HttpRequest.newBuilder(baseUri.resolve(compression)) - .build() - - //when - val body = httpClient.send(request, BodyHandlers.ofDecompressing()).body() - val json = IOUtils.toString(body, StandardCharsets.UTF_8) - - //then - assertThat(json, isJson()) + private val httpClient = HttpClient.newHttpClient() + + @ParameterizedTest + @MethodSource("compressedData") + fun shouldCreate(compressedString: CompressedString) { + //given + val uri = URI.create("${wm.runtimeInfo.httpBaseUrl}/data") + stubFor( + get("/data").willReturn( + ok() + .withHeader("Content-Encoding", compressedString.compression) + .withBody(compressedString.compressed) + ) + ) + + val request = HttpRequest.newBuilder(uri) + .build() + + //when + val body = httpClient.send(request, BodyHandlers.ofDecompressing()).body() + val actual = IOUtils.toString(body, StandardCharsets.UTF_8) + + //then + actual shouldBe compressedString.original + } + + companion object { + @JvmStatic + fun compressedData(): List { + val stringArb = Arb.string(32..64) + return listOf( + stringArb.map { CompressedString(it, "gzip", Compression.gzip(it)) }.next(), + stringArb.map { CompressedString(it, "deflate", Compression.deflate(it)) }.next() + ) } + + @RegisterExtension + @JvmStatic + var wm = WireMockExtension.newInstance() + .configureStaticDsl(true) + .failOnUnmatchedRequests(true) + .options(WireMockConfiguration.wireMockConfig().dynamicPort()) + .build() + } + + internal data class CompressedString(val original: String, val compression: String, val compressed: ByteArray) } \ No newline at end of file