All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hexagonkt.http.test.examples.ClientTest.kt Maven / Gradle / Ivy

package com.hexagonkt.http.test.examples

import com.hexagonkt.core.require
import com.hexagonkt.core.media.APPLICATION_JSON
import com.hexagonkt.core.media.APPLICATION_YAML
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.client.model.HttpClientRequest
import com.hexagonkt.http.client.model.HttpClientResponse
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.*
import com.hexagonkt.http.server.handlers.HttpCallback
import com.hexagonkt.http.server.handlers.HttpHandler
import com.hexagonkt.http.server.handlers.path
import com.hexagonkt.http.test.BaseTest
import com.hexagonkt.serialization.SerializationFormat
import com.hexagonkt.serialization.SerializationManager
import com.hexagonkt.serialization.serialize
import org.junit.jupiter.api.*

import java.math.BigInteger
import java.net.URL
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 ClientTest(
    final override val clientAdapter: () -> HttpClientPort,
    final override val serverAdapter: () -> HttpServerPort,
    private val serializationFormats: List,
    final override val serverSettings: HttpServerSettings = HttpServerSettings(),
) : BaseTest() {

    private var callback: HttpCallback = { 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()
        SerializationManager.defaultFormat = serializationFormats.firstOrNull()
    }

    @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 `Exceptions are returned as internal server errors`() {
        callback = { error("failure") }

        val response = client.send(HttpClientRequest())

        assertEquals(INTERNAL_SERVER_ERROR_500, response.status)
        assertTrue(response.bodyString().contains("failure"))
    }

    @Test 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(
            HttpClientRequest(
                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"
        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", secure = true),
            ))
        }

        client.cookies = emptyList()
        val response = client.send(
            HttpClientRequest(
                cookies = listOf(
                    Cookie("c1", "v1"),
                    Cookie("c2", "v2", 1),
                    Cookie("c3", "v3", secure = true),
                )
            )
        )

        val responseC4 = response.cookiesMap().require("c4")
        assertEquals("v4", responseC4.value)
        assertTrue(responseC4.maxAge in 59..60)
        assertEquals(Cookie("c5", "v5", secure = true), response.cookiesMap()["c5"])

        val clientC4 = client.cookiesMap().require("c4")
        assertEquals("v4", clientC4.value)
        assertTrue(clientC4.maxAge in 59..60)
        assertEquals(Cookie("c5", "v5", secure = true), client.cookiesMap()["c5"])
    }

    @Test fun `Create HTTP clients`() {
        val adapter = clientAdapter()

        // clientCreation
        HttpClient(adapter)
        HttpClient(adapter, URL("http://host:1234/base"))
        // clientCreation

        // clientSettingsCreation
        // All client settings parameters are optionals and provide default values
        HttpClient(adapter, HttpClientSettings(
            baseUrl = URL("http://host:1234/base"),
            contentType = ContentType(APPLICATION_JSON),
            useCookies = true,
            headers = Headers(Header("x-api-Key", "cafebabe")), // Headers used in all requests
            insecure = false,               // If true, the client doesn't check server certificates
            sslSettings = SslSettings()     // Key stores settings (check TLS section for details)
        ))
        // clientSettingsCreation
    }

    @Test fun `JSON requests works as expected`() {
        val expectedBody = "{  \"foo\" : \"fighters\",  \"es\" : \"áéíóúÁÉÍÓÚñÑ\"}"
        val map = mapOf("foo" to "fighters", "es" to "áéíóúÁÉÍÓÚñÑ")
        val requestBody = map.serialize(APPLICATION_JSON)

        val response = client.post("/", requestBody, contentType = ContentType(APPLICATION_JSON))
        assertEquals(expectedBody, response.body.toString().trim().replace("[\r\n]".toRegex(), ""))
        assertEquals(ContentType(APPLICATION_JSON).text, response.headers["ct"]?.value)

        val body2 = client.post("/", requestBody).body
        assertEquals(expectedBody, body2.toString().trim().replace("[\r\n]".toRegex(), ""))
    }

    @Test fun `HTTP generic requests work ok`() {

        // genericRequest
        val request = HttpClientRequest(
            method = GET,
            path = "/",
            body = mapOf("body" to "payload").serialize(),
            headers = Headers(Header("x-header", "value")),
            queryParameters = QueryParameters(QueryParameter("qp", "qpValue")),
            contentType = ContentType(APPLICATION_JSON)
        )

        val response = client.send(request)
        // genericRequest

        val getResponse = client.get("/queryParameters?qp=qpValue")
        assertEquals("qp=qpValue", getResponse.headers["query-parameters"]?.value)
        assertEquals("qp=qpValue", response.headers["query-parameters"]?.value)
        checkResponse(response, mapOf("body" to "payload"))
    }

    @Test fun `HTTP methods without body work ok`() {

        // withoutBodyRequests
        val responseGet = client.get("/")
        val responseHead = client.head("/")
        val responsePost = client.post("/")
        val responsePut = client.put("/")
        val responseDelete = client.delete("/")
        val responseTrace = client.trace("/")
        val responseOptions = client.options("/")
        val responsePatch = client.patch("/")
        // withoutBodyRequests

        checkResponse(responseGet, null)
        checkResponse(responseHead, null)
        checkResponse(responsePost, null)
        checkResponse(responsePut, null)
        checkResponse(responseDelete, null)
        checkResponse(responseTrace, null)
        checkResponse(responseOptions, null)
        checkResponse(responsePatch, null)
    }

    @Test fun `HTTP methods with body work ok`() {

        // bodyRequests
        val body = mapOf("key" to "value")
        val serializedBody = body.serialize()

        val responseGet = client.get("/", body = serializedBody)
        val responsePost = client.post("/", serializedBody)
        val responsePut = client.put("/", serializedBody)
        val responseDelete = client.delete("/", serializedBody)
        val responseTrace = client.trace("/", serializedBody)
        val responseOptions = client.options("/", serializedBody)
        val responsePatch = client.patch("/", serializedBody)
        // bodyRequests

        checkResponse(responseGet, body)
        checkResponse(responsePost, body)
        checkResponse(responsePut, body)
        checkResponse(responseDelete, body)
        checkResponse(responseTrace, body)
        checkResponse(responseOptions, body)
        checkResponse(responsePatch, body)
    }

    @Test fun `HTTP methods with body and content type work ok`() {

        // bodyAndContentTypeRequests
        val body = mapOf("key" to "value")
        val serializedBody = body.serialize(APPLICATION_YAML)
        val yaml = ContentType(APPLICATION_YAML)

        val responseGet = client.get("/", body = serializedBody, contentType = yaml)
        val responsePost = client.post("/", serializedBody, contentType = yaml)
        val responsePut = client.put("/", serializedBody, contentType = yaml)
        val responseDelete = client.delete("/", serializedBody, contentType = yaml)
        val responseTrace = client.trace("/", serializedBody, contentType = yaml)
        val responseOptions = client.options("/", serializedBody, contentType = yaml)
        val responsePatch = client.patch("/", serializedBody, contentType = yaml)
        // bodyAndContentTypeRequests

        checkResponse(responseGet, body, yaml)
        checkResponse(responsePost, body, yaml)
        checkResponse(responsePut, body, yaml)
        checkResponse(responseDelete, body, yaml)
        checkResponse(responseTrace, body, yaml)
        checkResponse(responseOptions, body, yaml)
        checkResponse(responsePatch, body, yaml)
    }

    @Test fun `Parameters are set properly` () {
        val endpoint = URL("http://localhost:${server.runtimePort}")
        val h = Headers(Header("header1", "val1", "val2"))
        val settings = HttpClientSettings(
            contentType = ContentType(APPLICATION_JSON),
            useCookies = false,
            headers = h,
            insecure = true
        )
        val c = HttpClient(clientAdapter(), endpoint, settings)

        assertEquals(c.settings.contentType, ContentType(APPLICATION_JSON))
        assert(!c.settings.useCookies)
        assertEquals(c.settings.headers, h)

        callback = {
            val headers = Header("head1", request.headers.require("header1").values)
            ok(headers = response.headers + headers)
        }

        c.use {
            it.start()
            it.get("/auth").apply {
                assertEquals(listOf("val1", "val2"), headers["head1"]?.values)
                assertEquals(status, OK_200)
            }
        }
    }

    @Test fun `Integers are sent properly` () {
        var run: Boolean

        callback = {
            val contentType = ContentType(APPLICATION_JSON, charset = Charsets.UTF_8)
            val number = BigInteger(request.body as ByteArray).toLong()
            ok(
                body = number,
                headers = response.headers + Header("body", number),
                contentType = contentType
            )
        }

        client.post("/string", 42).apply {
            assertEquals("42", headers.require("body").value)
            assertEquals(status, OK_200)
            run = true
        }

        assert(run)
    }

    @Test fun `Strings are sent properly` () {
        var run: Boolean

        client.post("/string", "text").apply {
            assert(headers["body"]?.value?.isNotEmpty() ?: false)
            assertEquals(status, OK_200)
            run = true
        }

        assert(run)
    }

    @Test fun `Request HTTPS example`() {

        val serverAdapter = serverAdapter()

        // Key store files
        val identity = "hexagonkt.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 = URL("classpath:ssl/$identity")
        val trustStore = URL("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 = URL("https://localhost:${server.runtimePort}"),
            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").value?.startsWith("CN=hexagonkt.com") ?: false)
            assertEquals(body, "Hello World!")
        }

        client.stop()
        server.stop()
    }

    private fun checkResponse(
        response: HttpClientResponse,
        parameter: Map?,
        format: ContentType = ContentType(APPLICATION_JSON),
    ) {

        assertEquals(OK_200, response.status)
        assertEquals(
            parameter?.serialize(format.mediaType)?.trim() ?: "",
            response.bodyString().trim()
        )
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy