diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b94fe55374..4340c3f6e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,3 +11,42 @@ jobs: uses: hexagontk/.github/.github/workflows/graalvm_gradle.yml@master with: check_directory: core/build + + # Jobs for debugging purposes, activate commenting 'if' + os_build: + strategy: + fail-fast: false + matrix: + os: [ windows-latest, macos-latest ] + + if: false + name: os_build (${{ matrix.os }}) + uses: hexagontk/.github/.github/workflows/graalvm_gradle.yml@master + with: + os: ${{ matrix.os }} + check_directory: core/build + + native_test: + strategy: + fail-fast: false + matrix: + os: [ windows-latest, macos-latest ] + + if: false + name: native_test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + steps: + - uses: al-cheb/configure-pagefile-action@v1.3 + if: ${{ matrix.os == 'windows-latest' }} + with: + minimum-size: 16GB + disk-root: "C:" + - uses: actions/checkout@v4 + with: + ref: develop + - uses: graalvm/setup-graalvm@v1 + with: + java-version: 21 + distribution: graalvm-community + cache: gradle + - run: ./gradlew --stacktrace nativeTest diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b0296f45e4..e74b84446c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -9,6 +9,8 @@ permissions: jobs: stale: runs-on: ubuntu-latest + permissions: + issues: write steps: - uses: actions/stale@v9 with: @@ -62,7 +64,7 @@ jobs: with: minimum-size: 16GB disk-root: "C:" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: develop - uses: graalvm/setup-graalvm@v1 diff --git a/core/README.md b/core/README.md index ba05616d5a..60ab345a24 100644 --- a/core/README.md +++ b/core/README.md @@ -55,3 +55,11 @@ Cryptography and key stores utilities. # Package com.hexagonkt.core.text Text utilities to allow the use of ANSI escape codes and case converting tools among other features. + +> TODO Create 'Pairs' class to model a list of pairs with repeatable keys +> Should allow to convert to list if keys are null, grouping values if there are duplicates +> Use it in serialization and HTTP fields (query params, forms, etc.) +> +> TODO Remove logger class and make log utilities extension methods (creating a loggerOf() helper) +> +> TODO logger and security may be removed and their content moved to root diff --git a/core/src/main/kotlin/com/hexagonkt/core/Uuids.kt b/core/src/main/kotlin/com/hexagonkt/core/Uuids.kt index 8c105ffe13..362145a05f 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/Uuids.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/Uuids.kt @@ -5,6 +5,47 @@ import com.hexagonkt.core.text.encodeToBase64 import java.nio.ByteBuffer import java.util.* +// TODO UUIDv7: https://antonz.org/uuidv7/?ref=dailydev#kotlin +/* + import java.security.SecureRandom + import java.time.Instant + + object UUIDv7 { + private val random = SecureRandom() + + fun generate(): ByteArray { + // random bytes + val value = ByteArray(16) + random.nextBytes(value) + + // current timestamp in ms + val timestamp = Instant.now().toEpochMilli() + + // timestamp + value[0] = ((timestamp shr 40) and 0xFF).toByte() + value[1] = ((timestamp shr 32) and 0xFF).toByte() + value[2] = ((timestamp shr 24) and 0xFF).toByte() + value[3] = ((timestamp shr 16) and 0xFF).toByte() + value[4] = ((timestamp shr 8) and 0xFF).toByte() + value[5] = (timestamp and 0xFF).toByte() + + // version and variant + value[6] = (value[6].toInt() and 0x0F or 0x70).toByte() + value[8] = (value[8].toInt() and 0x3F or 0x80).toByte() + + return value + } + + @JvmStatic + fun main(args: Array) { + val uuidVal = generate() + uuidVal.forEach { b -> print("%02x".format(b)) } + println() + } + } + */ +// TODO Rename to Ids.kt and support other algorithms (nanoid, ulid, snowflake, cuid) + /** * [TODO](https://github.com/hexagontk/hexagon/issues/271). * diff --git a/core/src/test/kotlin/com/hexagonkt/core/security/KeyStoresTest.kt b/core/src/test/kotlin/com/hexagonkt/core/security/KeyStoresTest.kt index 521a68432c..bd05bde56a 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/security/KeyStoresTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/security/KeyStoresTest.kt @@ -2,17 +2,13 @@ package com.hexagonkt.core.security import com.hexagonkt.core.urlOf import org.junit.jupiter.api.Test -import org.junit.jupiter.api.condition.DisabledOnOs -import org.junit.jupiter.api.condition.OS.WINDOWS import java.io.File import java.security.cert.X509Certificate import kotlin.test.assertEquals internal class KeyStoresTest { - @Test - @DisabledOnOs(WINDOWS) // TODO Fails in windows because Algorithm HmacPBESHA256 not available - fun `Key stores are loaded correctly`() { + @Test fun `Key stores are loaded correctly`() { val n = "hexagontk" val f = "$n.p12" val pwd = f.reversed() diff --git a/gradle.properties b/gradle.properties index 582f063de9..2c02c777c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.warning.mode=all org.gradle.console=plain # Gradle -version=3.6.1 +version=3.6.2 group=com.hexagonkt description=The atoms of your platform diff --git a/gradle/certificates.gradle b/gradle/certificates.gradle index 879b208b06..169b79734b 100644 --- a/gradle/certificates.gradle +++ b/gradle/certificates.gradle @@ -160,5 +160,7 @@ private static String createSan( final List allSubdomains = testSubdomains + [ "${domain}.test", "localhost" ] final List fullSubdomains = allSubdomains.collect { "dns:${it}".toString() } + // TODO Useful if IP should be added, refactor to support scenario +// fullSubdomains.add("ip:127.0.0.1") return fullSubdomains.join(",") } diff --git a/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt b/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt index 2272ca36e6..c8a3aac2eb 100644 --- a/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt +++ b/http/http_client/src/main/kotlin/com/hexagonkt/http/client/HttpClient.kt @@ -221,9 +221,7 @@ class HttpClient( private fun HttpHandler.process( request: HttpRequestPort, attributes: Map ): HttpContext = - HttpContext(HttpCall(request = request), handlerPredicate, attributes = attributes) - .let { context -> - if (handlerPredicate(context)) process(context) as HttpContext - else context - } + processHttp( + HttpContext(HttpCall(request = request), handlerPredicate, attributes = attributes) + ) } diff --git a/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt b/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt index a9ec50296e..3de8f223c9 100644 --- a/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt +++ b/http/http_client/src/test/kotlin/com/hexagonkt/http/client/HttpClientTest.kt @@ -1,10 +1,13 @@ package com.hexagonkt.http.client import com.hexagonkt.core.media.TEXT_CSV +import com.hexagonkt.core.media.TEXT_PLAIN import com.hexagonkt.core.urlOf import com.hexagonkt.http.handlers.FilterHandler +import com.hexagonkt.http.handlers.HttpPredicate import com.hexagonkt.http.model.HttpResponsePort import com.hexagonkt.http.model.* +import com.hexagonkt.http.patterns.LiteralPathPattern import java.lang.StringBuilder import java.util.concurrent.Flow import java.util.concurrent.Flow.Subscription @@ -139,6 +142,15 @@ internal class HttpClientTest { assertEquals("HTTP client *MUST BE STARTED* before shut-down", message) } + @Test fun `HTTP clients fails to start if already started`() { + val client = HttpClient(VoidAdapter) + client.start() + assert(client.started()) + val message = assertFailsWith { client.start() }.message + assertEquals("HTTP client is already started", message) + client.stop() + } + @Test fun `Handlers filter requests and responses`() { val handler = FilterHandler { val next = receive(body = "p_" + request.bodyString()).next() @@ -155,6 +167,38 @@ internal class HttpClientTest { } } + @Test fun `Request is sent even if no handler`() { + val pathPattern = LiteralPathPattern("/test") + val handler = FilterHandler(HttpPredicate(pathPattern = pathPattern)) { error("Failure") } + val client = HttpClient(VoidAdapter, handler = handler) + + val e1 = assertFailsWith { client.get("http://localhost") } + assertEquals("HTTP client *MUST BE STARTED* before sending requests", e1.message) + + client.start() + client.request { + val e2 = assertFailsWith { client.put("/test", "body") } + assertEquals("Failure", e2.message) + assertEquals("/good", client.put("/good", "body").headers["-path-"]?.string()) + } + } + + @Test fun `Request is sent with accept and authorization headers`() { + val handler = FilterHandler { + assertEquals(TEXT_PLAIN, request.accept.first().mediaType) + assertEquals("basic", request.authorization?.type) + assertEquals("abc", request.authorization?.value) + next() + } + val client = HttpClient(VoidAdapter, handler = handler) + + client.request { + val accept = listOf(ContentType(TEXT_PLAIN)) + val authorization = Authorization("basic", "abc") + client.send(HttpRequest(accept = accept, authorization = authorization)) + } + } + @Test fun `SSE requests work properly`() { val client = HttpClient(VoidAdapter) diff --git a/http/http_client_java/src/main/kotlin/com/hexagonkt/http/client/java/JavaClientAdapter.kt b/http/http_client_java/src/main/kotlin/com/hexagonkt/http/client/java/JavaClientAdapter.kt index bd3807ef64..5d32aa4fcc 100644 --- a/http/http_client_java/src/main/kotlin/com/hexagonkt/http/client/java/JavaClientAdapter.kt +++ b/http/http_client_java/src/main/kotlin/com/hexagonkt/http/client/java/JavaClientAdapter.kt @@ -152,10 +152,14 @@ class JavaClientAdapter : HttpClientPort { .newBuilder(URI(uri)) .method(request.method.toString(), BodyPublishers.ofByteArray(bodyBytes)) - request.headers.forEach { e -> - val name = e.value.name - if (name != "accept-encoding") // TODO Maybe accept-encoding interferes with H2C - e.value.values.forEach { h -> javaRequest.setHeader(name, h.toString()) } + request.headers.forEach { h -> + val name = h.value.name + val values = h.value.values + // TODO Maybe accept-encoding interferes with H2C + if (name != "accept-encoding" && values.isNotEmpty()) { + val kvs = values.flatMap { v -> listOf(name, v.toString()) }.toTypedArray() + javaRequest.headers(*kvs) + } } request.contentType?.let { javaRequest.setHeader("content-type", it.text) } diff --git a/http/http_client_java/src/test/kotlin/com/hexagonkt/http/client/java/AdapterExamplesTest.kt b/http/http_client_java/src/test/kotlin/com/hexagonkt/http/client/java/AdapterExamplesTest.kt index 6d1293c4f6..c3247702ec 100644 --- a/http/http_client_java/src/test/kotlin/com/hexagonkt/http/client/java/AdapterExamplesTest.kt +++ b/http/http_client_java/src/test/kotlin/com/hexagonkt/http/client/java/AdapterExamplesTest.kt @@ -5,33 +5,23 @@ import com.hexagonkt.http.test.examples.* import com.hexagonkt.serialization.jackson.JacksonTextFormat import com.hexagonkt.serialization.jackson.json.Json import com.hexagonkt.serialization.jackson.yaml.Yaml -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.condition.DisabledOnOs -import org.junit.jupiter.api.condition.OS.WINDOWS val clientAdapter: () -> JavaClientAdapter = ::JavaClientAdapter val serverAdapter: () -> JettyServletAdapter = ::JettyServletAdapter val formats: List = listOf(Json, Yaml) +// TODO Add multipart and file upload tests internal class AdapterBooksTest : BooksTest(clientAdapter, serverAdapter) internal class AdapterErrorsTest : ErrorsTest(clientAdapter, serverAdapter) internal class AdapterFiltersTest : FiltersTest(clientAdapter, serverAdapter) -internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) { - // TODO - @Test @Disabled override fun `Parameters are set properly`() {} - // TODO - @Test @Disabled override fun `Form parameters are sent correctly`() {} -} -@DisabledOnOs(WINDOWS) // TODO Make this work on GitHub runners +internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientCookiesTest : ClientCookiesTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttp2Test : ClientHttp2Test(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttpsTest : ClientHttpsTest(clientAdapter, serverAdapter, formats) internal class AdapterHttpsTest : HttpsTest(clientAdapter, serverAdapter) internal class AdapterZipTest : ZipTest(clientAdapter, serverAdapter) internal class AdapterCookiesTest : CookiesTest(clientAdapter, serverAdapter) -@Disabled // TODO internal class AdapterFilesTest : FilesTest(clientAdapter, serverAdapter) internal class AdapterCorsTest : CorsTest(clientAdapter, serverAdapter) -internal class AdapterSamplesTest : SamplesTest(clientAdapter, serverAdapter) { - // TODO - @Test @Disabled override fun callbacks() {} -} +internal class AdapterSamplesTest : SamplesTest(clientAdapter, serverAdapter) internal class AdapterBenchmarkIT : BenchmarkIT(clientAdapter, serverAdapter) diff --git a/http/http_handlers/README.md b/http/http_handlers/README.md index 6746cb74e1..aa6c44ebad 100644 --- a/http/http_handlers/README.md +++ b/http/http_handlers/README.md @@ -236,7 +236,7 @@ HTML Form processing. Don't parse body! ## File Uploads Multipart Requests -@code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?callbackFile +@code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartSamplesTest.kt?callbackFile ## Response Response information is provided by the `response` field: diff --git a/http/http_handlers/api/http_handlers.api b/http/http_handlers/api/http_handlers.api index 6eafd8212e..0f9c7a923b 100644 --- a/http/http_handlers/api/http_handlers.api +++ b/http/http_handlers/api/http_handlers.api @@ -21,6 +21,7 @@ public final class com/hexagonkt/http/handlers/AfterHandler : com/hexagonkt/hand public fun hashCode ()I public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public fun toString ()Ljava/lang/String; } @@ -47,6 +48,7 @@ public final class com/hexagonkt/http/handlers/BeforeHandler : com/hexagonkt/han public fun hashCode ()I public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public fun toString ()Ljava/lang/String; } @@ -71,6 +73,7 @@ public final class com/hexagonkt/http/handlers/ExceptionHandler : com/hexagonkt/ public fun hashCode ()I public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public fun toString ()Ljava/lang/String; } @@ -97,6 +100,7 @@ public final class com/hexagonkt/http/handlers/FilterHandler : com/hexagonkt/han public fun hashCode ()I public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public fun toString ()Ljava/lang/String; } @@ -300,6 +304,7 @@ public final class com/hexagonkt/http/handlers/HttpController$DefaultImpls { public static fun getPredicate (Lcom/hexagonkt/http/handlers/HttpController;)Lkotlin/jvm/functions/Function1; public static fun process (Lcom/hexagonkt/http/handlers/HttpController;Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public static fun process (Lcom/hexagonkt/http/handlers/HttpController;Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public static fun processHttp (Lcom/hexagonkt/http/handlers/HttpController;Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; } public abstract interface class com/hexagonkt/http/handlers/HttpExceptionCallback : kotlin/jvm/functions/Function2 { @@ -311,12 +316,14 @@ public abstract interface class com/hexagonkt/http/handlers/HttpHandler : com/he public abstract fun filter (Lcom/hexagonkt/http/model/HttpMethod;)Lcom/hexagonkt/http/handlers/HttpHandler; public abstract fun getHandlerPredicate ()Lcom/hexagonkt/http/handlers/HttpPredicate; public abstract fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public abstract fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; } public final class com/hexagonkt/http/handlers/HttpHandler$DefaultImpls { public static fun byMethod (Lcom/hexagonkt/http/handlers/HttpHandler;)Ljava/util/Map; public static fun filter (Lcom/hexagonkt/http/handlers/HttpHandler;Lcom/hexagonkt/http/model/HttpMethod;)Lcom/hexagonkt/http/handlers/HttpHandler; public static fun process (Lcom/hexagonkt/http/handlers/HttpHandler;Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public static fun processHttp (Lcom/hexagonkt/http/handlers/HttpHandler;Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; } public final class com/hexagonkt/http/handlers/HttpPredicate : kotlin/jvm/functions/Function1 { @@ -369,6 +376,7 @@ public final class com/hexagonkt/http/handlers/OnHandler : com/hexagonkt/handler public fun hashCode ()I public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public fun toString ()Ljava/lang/String; } @@ -393,6 +401,7 @@ public final class com/hexagonkt/http/handlers/PathHandler : com/hexagonkt/handl public fun hashCode ()I public fun process (Lcom/hexagonkt/handlers/Context;)Lcom/hexagonkt/handlers/Context; public fun process (Lcom/hexagonkt/http/model/HttpRequestPort;)Lcom/hexagonkt/http/handlers/HttpContext; + public fun processHttp (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public fun toString ()Ljava/lang/String; } diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt index 8f7dffd0a2..e804f7b52a 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpHandler.kt @@ -41,9 +41,10 @@ sealed interface HttpHandler : Handler { this } + fun processHttp(context: HttpContext): HttpContext = + if (handlerPredicate(context)) process(context) as HttpContext + else context + fun process(request: HttpRequestPort): HttpContext = - HttpContext(HttpCall(request = request), handlerPredicate).let { context -> - if (handlerPredicate(context)) process(context) as HttpContext - else context - } + processHttp(HttpContext(HttpCall(request = request), handlerPredicate)) } diff --git a/http/http_server/README.md b/http/http_server/README.md index 6747462a01..01d3e21dce 100644 --- a/http/http_server/README.md +++ b/http/http_server/README.md @@ -231,7 +231,7 @@ HTML Form processing. Don't parse body! ## File Uploads Multipart Requests -@code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?callbackFile +@code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartSamplesTest.kt?callbackFile ## Response Response information is provided by the `response` field: diff --git a/http/http_server_helidon/src/test/kotlin/com/hexagonkt/http/server/helidon/AdapterExamplesTest.kt b/http/http_server_helidon/src/test/kotlin/com/hexagonkt/http/server/helidon/AdapterExamplesTest.kt index 4ee5055248..2b59154fd8 100644 --- a/http/http_server_helidon/src/test/kotlin/com/hexagonkt/http/server/helidon/AdapterExamplesTest.kt +++ b/http/http_server_helidon/src/test/kotlin/com/hexagonkt/http/server/helidon/AdapterExamplesTest.kt @@ -5,31 +5,25 @@ import com.hexagonkt.http.test.examples.* import com.hexagonkt.serialization.jackson.JacksonTextFormat import com.hexagonkt.serialization.jackson.json.Json import com.hexagonkt.serialization.jackson.yaml.Yaml -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.condition.DisabledOnOs -import org.junit.jupiter.api.condition.OS.WINDOWS val clientAdapter: () -> JettyClientAdapter = ::JettyClientAdapter val serverAdapter: () -> HelidonServerAdapter = ::HelidonServerAdapter val formats: List = listOf(Json, Yaml) +// TODO Add multipart, SSE and WebSockets test internal class AdapterBooksTest : BooksTest(clientAdapter, serverAdapter) internal class AdapterErrorsTest : ErrorsTest(clientAdapter, serverAdapter) internal class AdapterFiltersTest : FiltersTest(clientAdapter, serverAdapter) -internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) { - // TODO Fix this case - @Test @Disabled override fun `Form parameters are sent correctly`() {} -} -@DisabledOnOs(WINDOWS) // TODO Make this work on GitHub runners +internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientCookiesTest : ClientCookiesTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttp2Test : ClientHttp2Test(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttpsTest : ClientHttpsTest(clientAdapter, serverAdapter, formats) internal class AdapterHttpsTest : HttpsTest(clientAdapter, serverAdapter) internal class AdapterZipTest : ZipTest(clientAdapter, serverAdapter) internal class AdapterCookiesTest : CookiesTest(clientAdapter, serverAdapter) internal class AdapterFilesTest : FilesTest(clientAdapter, serverAdapter) +internal class AdapterMultipartTest : MultipartTest(clientAdapter, serverAdapter) internal class AdapterCorsTest : CorsTest(clientAdapter, serverAdapter) internal class AdapterSamplesTest : SamplesTest(clientAdapter, serverAdapter) +internal class AdapterMultipartSamplesTest : MultipartSamplesTest(clientAdapter, serverAdapter) internal class AdapterBenchmarkIT : BenchmarkIT(clientAdapter, serverAdapter) -@Disabled -internal class AdapterSseTest : SseTest(clientAdapter, serverAdapter) -@Disabled -internal class AdapterWebSocketsTest : WebSocketsTest(clientAdapter, serverAdapter) diff --git a/http/http_server_jetty/src/test/kotlin/com/hexagonkt/http/server/jetty/AdapterExamplesTest.kt b/http/http_server_jetty/src/test/kotlin/com/hexagonkt/http/server/jetty/AdapterExamplesTest.kt index a0f8af55ca..8dad689f4f 100644 --- a/http/http_server_jetty/src/test/kotlin/com/hexagonkt/http/server/jetty/AdapterExamplesTest.kt +++ b/http/http_server_jetty/src/test/kotlin/com/hexagonkt/http/server/jetty/AdapterExamplesTest.kt @@ -5,24 +5,26 @@ import com.hexagonkt.http.test.examples.* import com.hexagonkt.serialization.jackson.JacksonTextFormat import com.hexagonkt.serialization.jackson.json.Json import com.hexagonkt.serialization.jackson.yaml.Yaml -import org.junit.jupiter.api.condition.DisabledOnOs -import org.junit.jupiter.api.condition.OS.WINDOWS val clientAdapter: () -> JettyClientAdapter = ::JettyClientAdapter val serverAdapter: () -> JettyServletAdapter = ::JettyServletAdapter val formats: List = listOf(Json, Yaml) +// TODO Add SSE and WebSockets test internal class AdapterBooksTest : BooksTest(clientAdapter, serverAdapter) internal class AdapterErrorsTest : ErrorsTest(clientAdapter, serverAdapter) internal class AdapterFiltersTest : FiltersTest(clientAdapter, serverAdapter) internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) -@DisabledOnOs(WINDOWS) // TODO Make this work on GitHub runners +internal class AdapterClientCookiesTest : ClientCookiesTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttp2Test : ClientHttp2Test(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttpsTest : ClientHttpsTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientMultipartTest : ClientMultipartTest(clientAdapter, serverAdapter, formats) internal class AdapterHttpsTest : HttpsTest(clientAdapter, serverAdapter) internal class AdapterZipTest : ZipTest(clientAdapter, serverAdapter) internal class AdapterCookiesTest : CookiesTest(clientAdapter, serverAdapter) internal class AdapterFilesTest : FilesTest(clientAdapter, serverAdapter) +internal class AdapterMultipartTest : MultipartTest(clientAdapter, serverAdapter) internal class AdapterCorsTest : CorsTest(clientAdapter, serverAdapter) internal class AdapterSamplesTest : SamplesTest(clientAdapter, serverAdapter) +internal class AdapterMultipartSamplesTest : MultipartSamplesTest(clientAdapter, serverAdapter) internal class AdapterBenchmarkIT : BenchmarkIT(clientAdapter, serverAdapter) -// TODO Implement also in Jetty -//internal class AdapterSseTest : SseTest(clientAdapter, serverAdapter) diff --git a/http/http_server_netty/src/test/kotlin/com/hexagonkt/http/server/netty/AdapterExamplesTest.kt b/http/http_server_netty/src/test/kotlin/com/hexagonkt/http/server/netty/AdapterExamplesTest.kt index 54c95f75a6..780fcc9fb5 100644 --- a/http/http_server_netty/src/test/kotlin/com/hexagonkt/http/server/netty/AdapterExamplesTest.kt +++ b/http/http_server_netty/src/test/kotlin/com/hexagonkt/http/server/netty/AdapterExamplesTest.kt @@ -6,9 +6,6 @@ import com.hexagonkt.serialization.jackson.JacksonTextFormat import com.hexagonkt.serialization.jackson.json.Json import com.hexagonkt.serialization.jackson.yaml.Yaml import org.junit.jupiter.api.condition.DisabledInNativeImage -import org.junit.jupiter.api.condition.DisabledOnOs -import org.junit.jupiter.api.condition.OS.MAC -import org.junit.jupiter.api.condition.OS.WINDOWS // TODO Assert context methods (request.method, request.protocol...) // TODO Check response headers don't contain invalid chars (\n, \t...) @@ -21,13 +18,18 @@ internal class AdapterBooksTest : BooksTest(clientAdapter, serverAdapter) internal class AdapterErrorsTest : ErrorsTest(clientAdapter, serverAdapter) internal class AdapterFiltersTest : FiltersTest(clientAdapter, serverAdapter) internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) -@DisabledOnOs(WINDOWS, MAC) // TODO Make this work on GitHub runners +internal class AdapterClientCookiesTest : ClientCookiesTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttp2Test : ClientHttp2Test(clientAdapter, serverAdapter, formats) +internal class AdapterClientHttpsTest : ClientHttpsTest(clientAdapter, serverAdapter, formats) +internal class AdapterClientMultipartTest : ClientMultipartTest(clientAdapter, serverAdapter, formats) internal class AdapterHttpsTest : HttpsTest(clientAdapter, serverAdapter) internal class AdapterZipTest : ZipTest(clientAdapter, serverAdapter) internal class AdapterCookiesTest : CookiesTest(clientAdapter, serverAdapter) internal class AdapterFilesTest : FilesTest(clientAdapter, serverAdapter) +internal class AdapterMultipartTest : MultipartTest(clientAdapter, serverAdapter) internal class AdapterCorsTest : CorsTest(clientAdapter, serverAdapter) internal class AdapterSamplesTest : SamplesTest(clientAdapter, serverAdapter) +internal class AdapterMultipartSamplesTest : MultipartSamplesTest(clientAdapter, serverAdapter) internal class AdapterBenchmarkIT : BenchmarkIT(clientAdapter, serverAdapter) internal class AdapterSseTest : SseTest(clientAdapter, serverAdapter) @DisabledInNativeImage // TODO Fix this @@ -45,10 +47,6 @@ val liteServerAdapter: () -> NettyServerAdapter = { internal class LiteAdapterBooksTest : BooksTest(clientAdapter, liteServerAdapter) internal class LiteAdapterErrorsTest : ErrorsTest(clientAdapter, liteServerAdapter) internal class LiteAdapterFiltersTest : FiltersTest(clientAdapter, liteServerAdapter) -@DisabledOnOs(WINDOWS, MAC) // TODO Make this work on GitHub runners -//@DisabledIf( -// "java.lang.System.getProperty('os.name').toLowerCase().contains('mac') && !java.lang.System.getProperty('org.graalvm.nativeimage.imagecode').isBlank()" -//) internal class LiteAdapterHttpsTest : HttpsTest(clientAdapter, liteServerAdapter) internal class LiteAdapterZipTest : ZipTest(clientAdapter, liteServerAdapter) internal class LiteAdapterCookiesTest : CookiesTest(clientAdapter, liteServerAdapter) diff --git a/http/http_server_netty_epoll/src/test/kotlin/com/hexagonkt/http/server/netty/epoll/AdapterExamplesTest.kt b/http/http_server_netty_epoll/src/test/kotlin/com/hexagonkt/http/server/netty/epoll/AdapterExamplesTest.kt index 799e5aa556..966cfbc43e 100644 --- a/http/http_server_netty_epoll/src/test/kotlin/com/hexagonkt/http/server/netty/epoll/AdapterExamplesTest.kt +++ b/http/http_server_netty_epoll/src/test/kotlin/com/hexagonkt/http/server/netty/epoll/AdapterExamplesTest.kt @@ -24,6 +24,14 @@ internal class AdapterFiltersTest : FiltersTest(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) internal class AdapterClientTest : ClientTest(clientAdapter, serverAdapter, formats) @EnabledOnOs(OS.LINUX) +internal class AdapterClientCookiesTest : ClientCookiesTest(clientAdapter, serverAdapter, formats) +@EnabledOnOs(OS.LINUX) +internal class AdapterClientHttp2Test : ClientHttp2Test(clientAdapter, serverAdapter, formats) +@EnabledOnOs(OS.LINUX) +internal class AdapterClientHttpsTest : ClientHttpsTest(clientAdapter, serverAdapter, formats) +@EnabledOnOs(OS.LINUX) +internal class AdapterClientMultipartTest : ClientMultipartTest(clientAdapter, serverAdapter, formats) +@EnabledOnOs(OS.LINUX) internal class AdapterHttpsTest : HttpsTest(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) internal class AdapterZipTest : ZipTest(clientAdapter, serverAdapter) @@ -32,10 +40,14 @@ internal class AdapterCookiesTest : CookiesTest(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) internal class AdapterFilesTest : FilesTest(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) +internal class AdapterMultipartTest : MultipartTest(clientAdapter, serverAdapter) +@EnabledOnOs(OS.LINUX) internal class AdapterCorsTest : CorsTest(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) internal class AdapterSamplesTest : SamplesTest(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) +internal class AdapterMultipartSamplesTest : MultipartSamplesTest(clientAdapter, serverAdapter) +@EnabledOnOs(OS.LINUX) internal class AdapterBenchmarkIT : BenchmarkIT(clientAdapter, serverAdapter) @EnabledOnOs(OS.LINUX) internal class AdapterSseTest : SseTest(clientAdapter, serverAdapter) diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/BenchmarkSimulation.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/BenchmarkSimulation.kt index 028fc74baf..573ad637e0 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/BenchmarkSimulation.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/BenchmarkSimulation.kt @@ -4,6 +4,7 @@ import io.gatling.javaapi.core.CoreDsl import io.gatling.javaapi.core.Simulation import io.gatling.javaapi.http.HttpDsl +// TODO Replace to use HTTP client with virtual threads class BenchmarkSimulation: Simulation() { private val protocol = System.getProperty("protocol") ?: "http" diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientCookiesTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientCookiesTest.kt new file mode 100644 index 0000000000..368ec95ab0 --- /dev/null +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientCookiesTest.kt @@ -0,0 +1,98 @@ +package com.hexagonkt.http.test.examples + +import com.hexagonkt.core.require +import com.hexagonkt.core.media.APPLICATION_JSON +import com.hexagonkt.http.client.HttpClientPort +import com.hexagonkt.http.model.HttpRequest +import com.hexagonkt.http.formatQueryString +import com.hexagonkt.http.model.* +import com.hexagonkt.http.server.* +import com.hexagonkt.http.handlers.HttpCallbackType +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.handlers.path +import com.hexagonkt.http.test.BaseTest +import com.hexagonkt.serialization.SerializationFormat +import com.hexagonkt.serialization.SerializationManager +import org.junit.jupiter.api.* + +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@Suppress("FunctionName") // This class's functions are intended to be used only in tests +abstract class ClientCookiesTest( + final override val clientAdapter: () -> HttpClientPort, + final override val serverAdapter: () -> HttpServerPort, + private val serializationFormats: List, + final override val serverSettings: HttpServerSettings = HttpServerSettings(), +) : BaseTest() { + + private var callback: HttpCallbackType = { this } + + override val handler: HttpHandler = path { + post("*") { callback() } + get("*") { callback() } + head("*") { callback() } + put("*") { callback() } + delete("*") { callback() } + trace("*") { callback() } + options("*") { callback() } + patch("*") { callback() } + } + + @BeforeAll fun setUpSerializationFormats() { + SerializationManager.formats = serializationFormats.toSet() + } + + @BeforeEach fun resetHandler() { + callback = { + val contentType = ContentType(APPLICATION_JSON, charset = Charsets.UTF_8) + val bodyString = request.bodyString() + val bodyHeader = + if (bodyString.endsWith("\n") || bodyString.contains("{")) "json" + else bodyString + + ok( + body = bodyString, + headers = response.headers + + Header("body", bodyHeader) + + Header("ct", request.contentType?.text ?: "") + + Header("query-parameters", formatQueryString(queryParameters)), + contentType = contentType, + ) + } + } + + @Test fun `Cookies are sent correctly`() { + callback = { + val cookiesMap = request.cookiesMap() + assertEquals(Cookie("c1", "v1"), cookiesMap["c1"]) + assertEquals(Cookie("c2", "v2", -1), cookiesMap["c2"]) + assertNull(cookiesMap["c3"]) // Secure headers only sent through HTTPS + ok(cookies = listOf( + Cookie("c4", "v4", 60), + Cookie("c5", "v5"), + Cookie("c6", "v6", secure = true), + )) + } + + client.cookies = emptyList() + val response = client.send( + HttpRequest( + cookies = listOf( + Cookie("c1", "v1"), + Cookie("c2", "v2", 1), + Cookie("c3", "v3", secure = true), + ) + ) + ) + + listOf(response.cookiesMap(), client.cookiesMap()).forEach { + val c4 = it.require("c4") + assertEquals("v4", c4.value) + assertTrue(c4.maxAge in 59..60) + assertEquals(Cookie("c5", "v5"), it["c5"]?.copy(domain = null)) + assertNull(it["c6"]) + } + } +} diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientHttp2Test.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientHttp2Test.kt new file mode 100644 index 0000000000..c46ef4affe --- /dev/null +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientHttp2Test.kt @@ -0,0 +1,134 @@ +package com.hexagonkt.http.test.examples + +import com.hexagonkt.core.require +import com.hexagonkt.core.media.APPLICATION_JSON +import com.hexagonkt.core.urlOf +import com.hexagonkt.http.SslSettings +import com.hexagonkt.http.client.HttpClient +import com.hexagonkt.http.client.HttpClientPort +import com.hexagonkt.http.client.HttpClientSettings +import com.hexagonkt.http.formatQueryString +import com.hexagonkt.http.model.* +import com.hexagonkt.http.server.* +import com.hexagonkt.http.handlers.HttpCallbackType +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.handlers.path +import com.hexagonkt.http.model.HttpProtocol.HTTP2 +import com.hexagonkt.http.test.BaseTest +import com.hexagonkt.serialization.SerializationFormat +import com.hexagonkt.serialization.SerializationManager +import org.junit.jupiter.api.* +import org.junit.jupiter.api.condition.DisabledIf +import java.net.URL + +import kotlin.test.assertEquals + +@Suppress("FunctionName") // This class's functions are intended to be used only in tests +abstract class ClientHttp2Test( + final override val clientAdapter: () -> HttpClientPort, + final override val serverAdapter: () -> HttpServerPort, + private val serializationFormats: List, + final override val serverSettings: HttpServerSettings = HttpServerSettings(), +) : BaseTest() { + + private var callback: HttpCallbackType = { this } + + override val handler: HttpHandler = path { + post("*") { callback() } + get("*") { callback() } + head("*") { callback() } + put("*") { callback() } + delete("*") { callback() } + trace("*") { callback() } + options("*") { callback() } + patch("*") { callback() } + } + + @BeforeAll fun setUpSerializationFormats() { + SerializationManager.formats = serializationFormats.toSet() + } + + @BeforeEach fun resetHandler() { + callback = { + val contentType = ContentType(APPLICATION_JSON, charset = Charsets.UTF_8) + val bodyString = request.bodyString() + val bodyHeader = + if (bodyString.endsWith("\n") || bodyString.contains("{")) "json" + else bodyString + + ok( + body = bodyString, + headers = response.headers + + Header("body", bodyHeader) + + Header("ct", request.contentType?.text ?: "") + + Header("query-parameters", formatQueryString(queryParameters)), + contentType = contentType, + ) + } + } + + @Test + @DisabledIf("nativeMac") + fun `Request HTTPS example`() { + + val serverAdapter = serverAdapter() + + // Key store files + val identity = "hexagontk.p12" + val trust = "trust.p12" + + // Default passwords are file name reversed + val keyStorePassword = identity.reversed() + val trustStorePassword = trust.reversed() + + // Key stores can be set as URIs to classpath resources (the triple slash is needed) + val keyStore = urlOf("classpath:ssl/$identity") + val trustStore = urlOf("classpath:ssl/$trust") + + val sslSettings = SslSettings( + keyStore = keyStore, + keyStorePassword = keyStorePassword, + trustStore = trustStore, + trustStorePassword = trustStorePassword, + clientAuth = true // Requires a valid certificate from the client (mutual TLS) + ) + + val serverSettings = serverSettings.copy( + bindPort = 0, + protocol = HTTP2, + sslSettings = sslSettings + ) + + val server = serve(serverAdapter, serverSettings) { + get("/hello") { + // We can access the certificate used by the client from the request + val subjectDn = request.certificate()?.subjectX500Principal?.name ?: "" + ok("Hello World!", headers = response.headers + Header("cert", subjectDn) ) + } + } + + // We'll use the same certificate for the client (in a real scenario it would be different) + val baseUrl = serverBase(server) + val clientSettings = HttpClientSettings(baseUrl = baseUrl, sslSettings = sslSettings) + + // Create an HTTP client and make an HTTPS request + val client = HttpClient(clientAdapter(), clientSettings) + client.start() + client.get("/hello").apply { + // Assure the certificate received (and returned) by the server is correct + assert(headers.require("cert").string()?.startsWith("CN=hexagontk.com") ?: false) + assertEquals(body, "Hello World!") + } + + client.stop() + server.stop() + } + + private fun serverBase(server: HttpServer): URL = + urlOf("${server.binding.protocol}://localhost:${server.runtimePort}") + + @Suppress("MemberVisibilityCanBePrivate") // Public access required by JUnit + fun nativeMac(): Boolean = + System.getProperty("os.name").lowercase().contains("mac") + && System.getProperty("org.graalvm.nativeimage.imagecode") != null +} diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientHttpsTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientHttpsTest.kt new file mode 100644 index 0000000000..52743fdff0 --- /dev/null +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientHttpsTest.kt @@ -0,0 +1,134 @@ +package com.hexagonkt.http.test.examples + +import com.hexagonkt.core.require +import com.hexagonkt.core.media.APPLICATION_JSON +import com.hexagonkt.core.urlOf +import com.hexagonkt.http.SslSettings +import com.hexagonkt.http.client.HttpClient +import com.hexagonkt.http.client.HttpClientPort +import com.hexagonkt.http.client.HttpClientSettings +import com.hexagonkt.http.formatQueryString +import com.hexagonkt.http.model.* +import com.hexagonkt.http.model.HttpProtocol.HTTPS +import com.hexagonkt.http.server.* +import com.hexagonkt.http.handlers.HttpCallbackType +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.handlers.path +import com.hexagonkt.http.test.BaseTest +import com.hexagonkt.serialization.SerializationFormat +import com.hexagonkt.serialization.SerializationManager +import org.junit.jupiter.api.* +import org.junit.jupiter.api.condition.DisabledIf +import java.net.URL + +import kotlin.test.assertEquals + +@Suppress("FunctionName") // This class's functions are intended to be used only in tests +abstract class ClientHttpsTest( + final override val clientAdapter: () -> HttpClientPort, + final override val serverAdapter: () -> HttpServerPort, + private val serializationFormats: List, + final override val serverSettings: HttpServerSettings = HttpServerSettings(), +) : BaseTest() { + + private var callback: HttpCallbackType = { this } + + override val handler: HttpHandler = path { + post("*") { callback() } + get("*") { callback() } + head("*") { callback() } + put("*") { callback() } + delete("*") { callback() } + trace("*") { callback() } + options("*") { callback() } + patch("*") { callback() } + } + + @BeforeAll fun setUpSerializationFormats() { + SerializationManager.formats = serializationFormats.toSet() + } + + @BeforeEach fun resetHandler() { + callback = { + val contentType = ContentType(APPLICATION_JSON, charset = Charsets.UTF_8) + val bodyString = request.bodyString() + val bodyHeader = + if (bodyString.endsWith("\n") || bodyString.contains("{")) "json" + else bodyString + + ok( + body = bodyString, + headers = response.headers + + Header("body", bodyHeader) + + Header("ct", request.contentType?.text ?: "") + + Header("query-parameters", formatQueryString(queryParameters)), + contentType = contentType, + ) + } + } + + @Test + @DisabledIf("nativeMac") + fun `Request HTTPS example`() { + + val serverAdapter = serverAdapter() + + // Key store files + val identity = "hexagontk.p12" + val trust = "trust.p12" + + // Default passwords are file name reversed + val keyStorePassword = identity.reversed() + val trustStorePassword = trust.reversed() + + // Key stores can be set as URIs to classpath resources (the triple slash is needed) + val keyStore = urlOf("classpath:ssl/$identity") + val trustStore = urlOf("classpath:ssl/$trust") + + val sslSettings = SslSettings( + keyStore = keyStore, + keyStorePassword = keyStorePassword, + trustStore = trustStore, + trustStorePassword = trustStorePassword, + clientAuth = true // Requires a valid certificate from the client (mutual TLS) + ) + + val serverSettings = serverSettings.copy( + bindPort = 0, + protocol = HTTPS, // You can also use HTTP2 + sslSettings = sslSettings + ) + + val server = serve(serverAdapter, serverSettings) { + get("/hello") { + // We can access the certificate used by the client from the request + val subjectDn = request.certificate()?.subjectX500Principal?.name ?: "" + ok("Hello World!", headers = response.headers + Header("cert", subjectDn) ) + } + } + + // We'll use the same certificate for the client (in a real scenario it would be different) + val baseUrl = serverBase(server) + val clientSettings = HttpClientSettings(baseUrl = baseUrl, sslSettings = sslSettings) + + // Create an HTTP client and make an HTTPS request + val client = HttpClient(clientAdapter(), clientSettings) + client.start() + client.get("/hello").apply { + // Assure the certificate received (and returned) by the server is correct + assert(headers.require("cert").string()?.startsWith("CN=hexagontk.com") ?: false) + assertEquals(body, "Hello World!") + } + + client.stop() + server.stop() + } + + private fun serverBase(server: HttpServer): URL = + urlOf("${server.binding.protocol}://localhost:${server.runtimePort}") + + @Suppress("MemberVisibilityCanBePrivate") // Public access required by JUnit + fun nativeMac(): Boolean = + System.getProperty("os.name").lowercase().contains("mac") + && System.getProperty("org.graalvm.nativeimage.imagecode") != null +} diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientMultipartTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientMultipartTest.kt new file mode 100644 index 0000000000..acf1c79669 --- /dev/null +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientMultipartTest.kt @@ -0,0 +1,84 @@ +package com.hexagonkt.http.test.examples + +import com.hexagonkt.core.media.APPLICATION_JSON +import com.hexagonkt.http.client.HttpClientPort +import com.hexagonkt.http.model.HttpRequest +import com.hexagonkt.http.formatQueryString +import com.hexagonkt.http.model.* +import com.hexagonkt.http.server.* +import com.hexagonkt.http.handlers.HttpCallbackType +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.handlers.path +import com.hexagonkt.http.test.BaseTest +import com.hexagonkt.serialization.SerializationFormat +import com.hexagonkt.serialization.SerializationManager +import org.junit.jupiter.api.* + +import kotlin.test.assertEquals + +@Suppress("FunctionName") // This class's functions are intended to be used only in tests +abstract class ClientMultipartTest( + final override val clientAdapter: () -> HttpClientPort, + final override val serverAdapter: () -> HttpServerPort, + private val serializationFormats: List, + final override val serverSettings: HttpServerSettings = HttpServerSettings(), +) : BaseTest() { + + private var callback: HttpCallbackType = { this } + + override val handler: HttpHandler = path { + post("*") { callback() } + get("*") { callback() } + head("*") { callback() } + put("*") { callback() } + delete("*") { callback() } + trace("*") { callback() } + options("*") { callback() } + patch("*") { callback() } + } + + @BeforeAll fun setUpSerializationFormats() { + SerializationManager.formats = serializationFormats.toSet() + } + + @BeforeEach fun resetHandler() { + callback = { + val contentType = ContentType(APPLICATION_JSON, charset = Charsets.UTF_8) + val bodyString = request.bodyString() + val bodyHeader = + if (bodyString.endsWith("\n") || bodyString.contains("{")) "json" + else bodyString + + ok( + body = bodyString, + headers = response.headers + + Header("body", bodyHeader) + + Header("ct", request.contentType?.text ?: "") + + Header("query-parameters", formatQueryString(queryParameters)), + contentType = contentType, + ) + } + } + + @Test open fun `Form parameters are sent correctly`() { + callback = { + val headers = Headers(formParameters.httpFields.map { (k, v) -> Header(k, v.values) }) + ok(headers = headers) + } + + val response = client.send( + HttpRequest( + formParameters = FormParameters( + FormParameter("p1", "v11"), + FormParameter("p2", "v21", "v22"), + ) + ) + ) + + val expectedHeaders = Headers(Header("p1", "v11"), Header("p2", "v21", "v22")) + val actualHeaders = + response.headers - "transfer-encoding" - "content-length" - "connection" - "date" + + assertEquals(expectedHeaders, actualHeaders) + } +} diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt index 7ea4881b08..fcca62d463 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt @@ -13,7 +13,6 @@ import com.hexagonkt.http.model.HttpResponsePort import com.hexagonkt.http.formatQueryString import com.hexagonkt.http.model.* import com.hexagonkt.http.model.HttpMethod.GET -import com.hexagonkt.http.model.HttpProtocol.HTTPS import com.hexagonkt.http.model.INTERNAL_SERVER_ERROR_500 import com.hexagonkt.http.model.OK_200 import com.hexagonkt.http.server.* @@ -25,16 +24,13 @@ import com.hexagonkt.serialization.SerializationFormat import com.hexagonkt.serialization.SerializationManager import com.hexagonkt.serialization.serialize import org.junit.jupiter.api.* -import org.junit.jupiter.api.condition.DisabledOnOs -import org.junit.jupiter.api.condition.OS.MAC -import org.junit.jupiter.api.condition.OS.WINDOWS import java.math.BigInteger import kotlin.test.assertEquals import kotlin.test.assertFalse -import kotlin.test.assertNull import kotlin.test.assertTrue +// TODO Refactor duplicated code @Suppress("FunctionName") // This class's functions are intended to be used only in tests abstract class ClientTest( final override val clientAdapter: () -> HttpClientPort, @@ -88,7 +84,7 @@ abstract class ClientTest( assertTrue(response.bodyString().contains("failure")) } - @Test open fun `Redirects are handled correctly correctly`() { + @Test fun `Redirects are handled correctly correctly`() { callback = { if (queryParameters["ok"] != null) ok("redirected") else found("/foo?ok") @@ -106,61 +102,6 @@ abstract class ClientTest( assertEquals("redirected", redirectedResponse.bodyString()) } - @Test open fun `Form parameters are sent correctly`() { - callback = { - val headers = Headers(formParameters.httpFields.map { (k, v) -> Header(k, v.values) }) - ok(headers = headers) - } - - val response = client.send( - HttpRequest( - formParameters = FormParameters( - FormParameter("p1", "v11"), - FormParameter("p2", "v21", "v22"), - ) - ) - ) - - val expectedHeaders = Headers(Header("p1", "v11"), Header("p2", "v21", "v22")) - val actualHeaders = - response.headers - "transfer-encoding" - "content-length" - "connection" - "date" - - assertEquals(expectedHeaders, actualHeaders) - } - - @Test fun `Cookies are sent correctly`() { - callback = { - val cookiesMap = request.cookiesMap() - assertEquals(Cookie("c1", "v1"), cookiesMap["c1"]) - assertEquals(Cookie("c2", "v2", -1), cookiesMap["c2"]) - assertNull(cookiesMap["c3"]) // Secure headers only sent through HTTPS - ok(cookies = listOf( - Cookie("c4", "v4", 60), - Cookie("c5", "v5"), - Cookie("c6", "v6", secure = true), - )) - } - - client.cookies = emptyList() - val response = client.send( - HttpRequest( - cookies = listOf( - Cookie("c1", "v1"), - Cookie("c2", "v2", 1), - Cookie("c3", "v3", secure = true), - ) - ) - ) - - listOf(response.cookiesMap(), client.cookiesMap()).forEach { - val c4 = it.require("c4") - assertEquals("v4", c4.value) - assertTrue(c4.maxAge in 59..60) - assertEquals(Cookie("c5", "v5"), it["c5"]?.copy(domain = null)) - assertNull(it["c6"]) - } - } - @Test fun `Create HTTP clients`() { val adapter = clientAdapter() @@ -298,7 +239,7 @@ abstract class ClientTest( checkResponse(responsePatch, body, yaml) } - @Test open fun `Parameters are set properly` () { + @Test fun `Parameters are set properly` () { val clientHeaders = Headers(Header("header1", "val1", "val2")) val settings = HttpClientSettings( baseUrl = server.binding, @@ -386,62 +327,6 @@ abstract class ClientTest( assert(run) } - @Test - @DisabledOnOs(WINDOWS, MAC) // TODO Make this work on GitHub runners - fun `Request HTTPS example`() { - - val serverAdapter = serverAdapter() - - // Key store files - val identity = "hexagontk.p12" - val trust = "trust.p12" - - // Default passwords are file name reversed - val keyStorePassword = identity.reversed() - val trustStorePassword = trust.reversed() - - // Key stores can be set as URIs to classpath resources (the triple slash is needed) - val keyStore = urlOf("classpath:ssl/$identity") - val trustStore = urlOf("classpath:ssl/$trust") - - val sslSettings = SslSettings( - keyStore = keyStore, - keyStorePassword = keyStorePassword, - trustStore = trustStore, - trustStorePassword = trustStorePassword, - clientAuth = true // Requires a valid certificate from the client (mutual TLS) - ) - - val serverSettings = serverSettings.copy( - bindPort = 0, - protocol = HTTPS, // You can also use HTTP2 - sslSettings = sslSettings - ) - - val server = serve(serverAdapter, serverSettings) { - get("/hello") { - // We can access the certificate used by the client from the request - val subjectDn = request.certificate()?.subjectX500Principal?.name ?: "" - ok("Hello World!", headers = response.headers + Header("cert", subjectDn) ) - } - } - - // We'll use the same certificate for the client (in a real scenario it would be different) - val clientSettings = HttpClientSettings(baseUrl = server.binding, sslSettings = sslSettings) - - // Create an HTTP client and make an HTTPS request - val client = HttpClient(clientAdapter(), clientSettings) - client.start() - client.get("/hello").apply { - // Assure the certificate received (and returned) by the server is correct - assert(headers.require("cert").string()?.startsWith("CN=hexagontk.com") ?: false) - assertEquals(body, "Hello World!") - } - - client.stop() - server.stop() - } - private fun checkResponse( response: HttpResponsePort, parameter: Map?, diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FilesTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FilesTest.kt index 43b35c3415..6309b41fbd 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FilesTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/FilesTest.kt @@ -4,11 +4,9 @@ import com.hexagonkt.core.media.TEXT_CSS import com.hexagonkt.core.media.TEXT_HTML import com.hexagonkt.core.urlOf import com.hexagonkt.http.client.HttpClientPort -import com.hexagonkt.http.model.HttpRequest import com.hexagonkt.http.model.* import com.hexagonkt.http.model.NOT_FOUND_404 import com.hexagonkt.http.model.HttpMethod.GET -import com.hexagonkt.http.model.HttpMethod.POST import com.hexagonkt.http.model.OK_200 import com.hexagonkt.http.server.HttpServerPort import com.hexagonkt.http.server.HttpServerSettings @@ -37,6 +35,7 @@ abstract class FilesTest( else "../http_test/src/main/resources/assets" } + // TODO Remove unnecessary handlers (restructure examples) // files private val path: PathHandler = path { @@ -95,17 +94,6 @@ abstract class FilesTest( override val handler: HttpHandler = path - @Test fun `Parameters are separated from each other`() { - val parts = listOf(HttpPart("name", "value")) - val response = client.send( - HttpRequest(POST, path = "/form?queryName=queryValue", parts = parts) - ) - assertEquals("queryName:queryValue", response.headers["query-params"]?.value) - assert(!(response.headers["query-params"]?.string()?.contains("name:value") ?: true)) - assert(response.headers["form-params"]?.string()?.contains("name:value") ?: false) - assert(!(response.headers["form-params"]?.string()?.contains("queryName:queryValue") ?: true)) - } - @Test fun `Requesting a folder with an existing file name returns 404`() { val response = client.get ("/file.txt/") assertResponseContains(response, NOT_FOUND_404) @@ -137,31 +125,6 @@ abstract class FilesTest( assertEquals(NOT_FOUND_404, client.get("/html/not_found.css").status) } - @Test fun `Sending multi part content works properly`() { - // clientForm - val parts = listOf(HttpPart("name", "value")) - val response = client.send(HttpRequest(POST, path = "/multipart", parts = parts)) - // clientForm - val expectedHeaders = Headers( - Header("name", "name"), - Header("body", "value"), - Header("size", "5"), - ) - expectedHeaders.forEach { - assertEquals(it.value, response.headers[it.key]) - } - } - - @Test fun `Sending files works properly`() { - // clientFile - val stream = urlOf("classpath:assets/index.html").readBytes() - val parts = listOf(HttpPart("file", stream, "index.html")) - val response = client.send(HttpRequest(POST, path = "/file", parts = parts)) - // clientFile - assertEquals("index.html", response.headers["submitted-file"]?.value) - assertResponseContains(response, OK_200, "", "Hexagon", "") - } - @Test fun `Files mounted on a path are returned properly`() { val response = client.get("/html/index.html") assertEquals(TEXT_HTML, response.contentType?.mediaType) diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt index cfaa3032e8..7e02d7765e 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt @@ -18,6 +18,8 @@ import com.hexagonkt.http.handlers.HttpHandler import com.hexagonkt.http.handlers.path import com.hexagonkt.http.test.BaseTest import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.DisabledIf +import java.net.URL import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertNotNull @@ -62,7 +64,9 @@ abstract class HttpsTest( override val handler: HttpHandler = router - @Test fun `Serve HTTPS example`() { + @Test + @DisabledIf("nativeMac") + fun `Serve HTTPS example`() { // https // Key store files @@ -105,7 +109,7 @@ abstract class HttpsTest( val clientSettings = HttpClientSettings(sslSettings = sslSettings) // Create an HTTP client and make an HTTPS request - val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = server.binding)) + val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = serverBase(server))) client.start() client.get("/hello").apply { // Assure the certificate received (and returned) by the server is correct @@ -118,11 +122,13 @@ abstract class HttpsTest( server.stop() } - @Test fun `Serve HTTPS works properly`() { + @Test + @DisabledIf("nativeMac") + fun `Serve HTTPS works properly`() { val server = serve(serverAdapter(), handler, http2ServerSettings.copy(protocol = HTTPS)) - val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = server.binding)) + val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = serverBase(server))) client.start() client.get("/hello").apply { assert(headers.require("cert").string()?.startsWith("CN=hexagontk.com") ?: false) @@ -133,11 +139,13 @@ abstract class HttpsTest( server.stop() } - @Test fun `Serve HTTP2 works properly`() { + @Test + @DisabledIf("nativeMac") + fun `Serve HTTP2 works properly`() { val server = serve(serverAdapter(), handler, http2ServerSettings) - val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = server.binding)) + val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = serverBase(server))) client.start() client.get("/hello").apply { assert(headers.require("cert").string()?.startsWith("CN=hexagontk.com") ?: false) @@ -148,7 +156,9 @@ abstract class HttpsTest( server.stop() } - @Test fun `Serve insecure HTTPS example`() { + @Test + @DisabledIf("nativeMac") + fun `Serve insecure HTTPS example`() { val identity = "hexagontk.p12" val trust = "trust.p12" @@ -185,8 +195,8 @@ abstract class HttpsTest( ) // Create an HTTP client and make an HTTPS request - val contextPath = server.binding - val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = contextPath)) + val base = serverBase(server) + val client = HttpClient(clientAdapter(), clientSettings.copy(baseUrl = base)) client.start() client.get("/hello").apply { assertEquals("Hello World!", body) @@ -195,7 +205,7 @@ abstract class HttpsTest( assertFails { val adapter = clientAdapter() val noTrustStore = HttpClientSettings() - HttpClient(adapter, noTrustStore.copy(baseUrl = contextPath)).use { + HttpClient(adapter, noTrustStore.copy(baseUrl = base)).use { it.start() it.get("/hello") } @@ -212,7 +222,7 @@ abstract class HttpsTest( val insecureClient = HttpClient( clientAdapter(), - clientSettings.copy(baseUrl = contextPath, insecure = true, sslSettings = SslSettings()) + clientSettings.copy(baseUrl = base, insecure = true, sslSettings = SslSettings()) ) insecureClient.use { @@ -223,7 +233,7 @@ abstract class HttpsTest( } val settings = clientSettings.copy( - baseUrl = contextPath, + baseUrl = base, insecure = false, sslSettings = SslSettings() ) @@ -248,4 +258,12 @@ abstract class HttpsTest( assertNotNull(getPublicKey("ca")) } } + + private fun serverBase(server: HttpServer): URL = + urlOf("${server.binding.protocol}://localhost:${server.runtimePort}") + + @Suppress("MemberVisibilityCanBePrivate") // Public access required by JUnit + fun nativeMac(): Boolean = + System.getProperty("os.name").lowercase().contains("mac") + && System.getProperty("org.graalvm.nativeimage.imagecode") != null } diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartSamplesTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartSamplesTest.kt new file mode 100644 index 0000000000..ac9141c472 --- /dev/null +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartSamplesTest.kt @@ -0,0 +1,41 @@ +package com.hexagonkt.http.test.examples + +import com.hexagonkt.core.urlOf +import com.hexagonkt.http.client.HttpClient +import com.hexagonkt.http.client.HttpClientPort +import com.hexagonkt.http.client.HttpClientSettings +import com.hexagonkt.http.model.HttpRequest +import com.hexagonkt.http.model.* +import com.hexagonkt.http.model.HttpMethod.* +import com.hexagonkt.http.server.HttpServer +import com.hexagonkt.http.server.HttpServerPort +import com.hexagonkt.http.server.HttpServerSettings +import org.junit.jupiter.api.Test + +abstract class MultipartSamplesTest( + val clientAdapter: () -> HttpClientPort, + val serverAdapter: () -> HttpServerPort, + val serverSettings: HttpServerSettings = HttpServerSettings(), +) { + @Test open fun callbacks() { + val server = HttpServer(serverAdapter()) { + // callbackFile + post("/file") { + val filePart = request.partsMap()["file"] ?: error("File not available") + ok(filePart.body) + } + // callbackFile + } + + server.use { s -> + s.start() + HttpClient(clientAdapter(), HttpClientSettings(s.binding)).use { + it.start() + val stream = urlOf("classpath:assets/index.html").readBytes() + val parts = listOf(HttpPart("file", stream, "index.html")) + val response = it.send(HttpRequest(POST, path = "/file", parts = parts)) + assert(response.bodyString().contains("Hexagon")) + } + } + } +} diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartTest.kt new file mode 100644 index 0000000000..fc46d52c0c --- /dev/null +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/MultipartTest.kt @@ -0,0 +1,134 @@ +package com.hexagonkt.http.test.examples + +import com.hexagonkt.core.media.TEXT_CSS +import com.hexagonkt.core.media.TEXT_HTML +import com.hexagonkt.core.urlOf +import com.hexagonkt.http.client.HttpClientPort +import com.hexagonkt.http.model.HttpRequest +import com.hexagonkt.http.model.* +import com.hexagonkt.http.model.NOT_FOUND_404 +import com.hexagonkt.http.model.HttpMethod.GET +import com.hexagonkt.http.model.HttpMethod.POST +import com.hexagonkt.http.model.OK_200 +import com.hexagonkt.http.server.HttpServerPort +import com.hexagonkt.http.server.HttpServerSettings +import com.hexagonkt.http.server.callbacks.FileCallback +import com.hexagonkt.http.server.callbacks.UrlCallback +import com.hexagonkt.http.handlers.PathHandler +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.handlers.path +import com.hexagonkt.http.test.BaseTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.io.File +import kotlin.test.assertEquals + +@TestInstance(PER_CLASS) +@Suppress("FunctionName") // This class's functions are intended to be used only in tests +abstract class MultipartTest( + final override val clientAdapter: () -> HttpClientPort, + final override val serverAdapter: () -> HttpServerPort, + final override val serverSettings: HttpServerSettings = HttpServerSettings(), +) : BaseTest() { + + private val directory = File("http_test/src/main/resources/assets").let { + if (it.exists()) it.path + else "../http_test/src/main/resources/assets" + } + + // TODO Remove unnecessary handlers + // files + private val path: PathHandler = path { + + // Serve `public` resources folder on `/*` + after( + methods = setOf(GET), + pattern = "/*", + status = NOT_FOUND_404, + callback = UrlCallback(urlOf("classpath:public")) + ) + + path("/static") { + get("/files/*", UrlCallback(urlOf("classpath:assets"))) + get("/resources/*", FileCallback(File(directory))) + } + + get("/html/*", UrlCallback(urlOf("classpath:assets"))) // Serve `assets` files on `/html/*` + get("/pub/*", FileCallback(File(directory))) // Serve `test` folder on `/pub/*` + + post("/multipart") { + val headers = parts.first().let { p -> + val name = p.name + val bodyString = p.bodyString() + val size = p.size.toString() + Headers( + Header("name", name), + Header("body", bodyString), + Header("size", size), + ) + } + + ok(headers = headers) + } + + post("/file") { + val part = parts.first() + val content = part.bodyString() + val submittedFile = part.submittedFileName ?: "" + ok(content, headers = response.headers + Header("submitted-file", submittedFile)) + } + + post("/form") { + fun serializeMap(map: Collection): List = listOf( + map.joinToString("\n") { "${it.name}:${it.values.joinToString(",")}" } + ) + + val queryParams = serializeMap(queryParameters.values) + val formParams = serializeMap(formParameters.values) + val headers = + Headers(Header("query-params", queryParams), Header("form-params", formParams)) + + ok(headers = response.headers + headers) + } + } + // files + + override val handler: HttpHandler = path + + @Test fun `Parameters are separated from each other`() { + val parts = listOf(HttpPart("name", "value")) + val response = client.send( + HttpRequest(POST, path = "/form?queryName=queryValue", parts = parts) + ) + assertEquals("queryName:queryValue", response.headers["query-params"]?.value) + assert(!(response.headers["query-params"]?.string()?.contains("name:value") ?: true)) + assert(response.headers["form-params"]?.string()?.contains("name:value") ?: false) + assert(!(response.headers["form-params"]?.string()?.contains("queryName:queryValue") ?: true)) + } + + @Test fun `Sending multi part content works properly`() { + // clientForm + val parts = listOf(HttpPart("name", "value")) + val response = client.send(HttpRequest(POST, path = "/multipart", parts = parts)) + // clientForm + val expectedHeaders = Headers( + Header("name", "name"), + Header("body", "value"), + Header("size", "5"), + ) + expectedHeaders.forEach { + assertEquals(it.value, response.headers[it.key]) + } + } + + @Test fun `Sending files works properly`() { + // clientFile + val stream = urlOf("classpath:assets/index.html").readBytes() + val parts = listOf(HttpPart("file", stream, "index.html")) + val response = client.send(HttpRequest(POST, path = "/file", parts = parts)) + // clientFile + assertEquals("index.html", response.headers["submitted-file"]?.value) + assertResponseContains(response, OK_200, "", "Hexagon", "") + } +} diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt index b26c8f66cf..803cacd8f9 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt @@ -32,7 +32,6 @@ abstract class SamplesTest( val serverAdapter: () -> HttpServerPort, val serverSettings: HttpServerSettings = HttpServerSettings(), ) { - @Test fun serverCreation() { // serverCreation /* @@ -287,13 +286,6 @@ abstract class SamplesTest( } // callbackFormParam - // callbackFile - post("/file") { - val filePart = request.partsMap()["file"] ?: error("File not available") - ok(filePart.body) - } - // callbackFile - // callbackRedirect get("/redirect") { send(FOUND_302, "/call") // browser redirect to /call @@ -346,11 +338,6 @@ abstract class SamplesTest( assertEquals(FOUND_302, it.get("/redirect").status) assertEquals(OK_200, it.get("/cookie").status) assertEquals(INTERNAL_SERVER_ERROR_500, it.get("/halt").status) - - val stream = urlOf("classpath:assets/index.html").readBytes() - val parts = listOf(HttpPart("file", stream, "index.html")) - val response = it.send(HttpRequest(POST, path = "/file", parts = parts)) - assert(response.bodyString().contains("Hexagon")) } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index b1b8133e2e..0edf4fb0e3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { // Build - version("kotlin", "2.0.20-RC") + version("kotlin", "2.0.10") version("dokka", "1.9.20") version("licenseReport", "2.8") version("binValidator", "0.16.3") @@ -35,22 +35,22 @@ dependencyResolutionManagement { version("junit", "5.10.3") version("mockk", "1.13.12") // TODO Latest version breaks stress test (consider using JMeter) - //version("gatling", "3.11.3") + // https://jmeter.apache.org/usermanual/build-programmatic-test-plan.html version("gatling", "3.10.5") version("jmh", "1.37") // Shared - version("slf4j", "2.0.13") + version("slf4j", "2.0.16") // http_server_netty version("netty", "4.1.112.Final") version("nettyTcNative", "2.0.65.Final") // http_server_helidon - version("helidon", "4.0.11") + version("helidon", "4.1.0") // http_server_servlet - version("servlet", "6.0.0") + version("servlet", "6.1.0") version("jetty", "12.0.12") // rest_tools diff --git a/site/build.gradle.kts b/site/build.gradle.kts index 731e78b53f..92728427e6 100644 --- a/site/build.gradle.kts +++ b/site/build.gradle.kts @@ -18,11 +18,7 @@ tasks.register("jacocoRootReport") { .filterNot { it.absolutePath.contains("http_test") } .filterNot { it.absolutePath.contains("serialization_test") } .filterNot { it.absolutePath.contains("templates_test") } - .filterNot { it.absolutePath.contains("rest") } - .filterNot { it.absolutePath.contains("rest_tools") } - .filterNot { it.absolutePath.contains("serverless_http") } .filterNot { it.absolutePath.contains("serverless_http_google") } - .filterNot { it.absolutePath.contains("web") } .toList() // TODO Include the filtered modules when they are ready @@ -132,7 +128,6 @@ tasks.register("installMkDocs") { exec { commandLine("python -m venv $venv".split(" ")) } exec { commandLine("$venv/bin/pip install mkdocs-material==$mkdocsMaterialVersion".split(" ")) } exec { commandLine("$venv/bin/pip install mkdocs-htmlproofer-plugin".split(" ")) } - exec { commandLine("$venv/bin/pip install mike".split(" ")) } } } diff --git a/site/mkdocs.yml b/site/mkdocs.yml index b31a46ff8c..fe59520381 100644 --- a/site/mkdocs.yml +++ b/site/mkdocs.yml @@ -2,7 +2,7 @@ dev_addr: 127.0.0.1:8000 site_name: Hexagon -site_url: https://hexagontk.com/ +site_url: https://hexagontk.com site_author: Hexagon Toolkit site_dir: build/site site_description: The atoms of your platform @@ -129,9 +129,6 @@ extra: provider: google property: G-BEKWF2E4DJ -# version: -# provider: mike - twitter_user: hexagontk social: - icon: fontawesome/brands/github