Skip to content

Commit

Permalink
test: http layer test improvements
Browse files Browse the repository at this point in the history
Signed-off-by: Sam Gammon <[email protected]>
  • Loading branch information
sgammon committed Feb 13, 2024
1 parent 67b2614 commit 0d3d6b4
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@
* License for the specific language governing permissions and limitations under the License.
*/

@file:Suppress("JVM_RECORD_REQUIRES_JDK15")

package elide.http

import kotlin.jvm.JvmInline
import kotlin.jvm.JvmRecord
import elide.http.api.HttpStatus.Type
import elide.http.api.HttpStatusCode
import elide.http.api.HttpString
import elide.http.api.HttpStatus as HttpStatusAPI

/**
*
*/
@JvmInline public value class HttpStatus(public val statusInfo: HttpStatusInfo) : HttpStatusAPI {
@JvmInline public value class HttpStatus(private val statusInfo: HttpStatusInfo) : HttpStatusAPI {
/**
*
*/
public data class HttpStatusInfo(
@JvmRecord public data class HttpStatusInfo(
public val code: HttpStatusCode,
public val reason: HttpString? = null,
)

override val code: HttpStatusCode get() = statusInfo.code
override val text: HttpString? get() = statusInfo.reason
override val type: Type? get() = Type.fromCode(code)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import kotlinx.datetime.Instant
import kotlin.jvm.JvmInline
import kotlin.jvm.JvmStatic
import kotlin.reflect.KClass
import elide.core.encoding.Encoding
import elide.core.encoding.Encoding.BASE64
import elide.http.api.HttpHeaders.HeaderName
import elide.util.Encoding
import elide.util.Encoding.BASE64

/**
* ## HTTP Header Information
Expand Down Expand Up @@ -114,7 +114,9 @@ internal sealed interface HeaderValueMultiTokenType<T> : HttpHeaderValue<T> wher
* @param token Token value.
*/
@JvmInline public value class HttpToken internal constructor (override val token: HttpString) :
HeaderValueTokenType<HttpString>, HttpHeaderValue<String>
HeaderValueTokenType<HttpString>, HttpHeaderValue<String> {
override fun toString(): String = asString
}

/**
* ## Generic Multi-Token
Expand All @@ -126,6 +128,7 @@ internal sealed interface HeaderValueMultiTokenType<T> : HttpHeaderValue<T> wher
@JvmInline public value class HttpTokenList internal constructor (override val tokens: List<String>) :
HeaderValueMultiTokenType<String>, HttpHeaderValue<String> {
override val asString: String get() = tokens.joinToString(DEFAULT_SEPARATOR)
override fun toString(): String = asString
}

/**
Expand All @@ -136,7 +139,9 @@ internal sealed interface HeaderValueMultiTokenType<T> : HttpHeaderValue<T> wher
* @param token Token value.
*/
@JvmInline public value class Language internal constructor (override val token: String) :
HeaderValueTokenType<String>, HttpHeaderValue<String>
HeaderValueTokenType<String>, HttpHeaderValue<String> {
override fun toString(): String = asString
}

/**
* ## MIME Type Token
Expand All @@ -156,7 +161,9 @@ internal sealed interface HeaderValueMultiTokenType<T> : HttpHeaderValue<T> wher
/** Well-known mimetype for a stream of bytes. */
public val OctetStream: Mimetype = Mimetype("application/octet-stream")
}
}

override fun toString(): String = asString
}

/**
* ## HTTP Encoding
Expand All @@ -171,6 +178,7 @@ internal sealed interface HeaderValueMultiTokenType<T> : HttpHeaderValue<T> wher
) : HeaderValueTokenType<String> {
override val token: String get() = spec.first
override val asString: String get() = token
override fun toString(): String = asString
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

package elide.http.api

import elide.util.Encoding
import elide.core.encoding.Encoding

/**
* # HTTP Headers: Standard Values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ import elide.http.api.HttpHeaders.HeaderValue
override fun toString(): String = asString
override val asString: String get() = values.joinToString(DEFAULT_SEPARATOR)
override val allValues: List<HttpString> get() = values.toList()
override val size: Int get() = values.size

internal fun add(value: String): MultiValue = MultiValue(
Array(values.size + 1) { i -> if (i == values.size) value else values[i] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

package elide.http.api

import kotlin.jvm.JvmStatic

/**
* # HTTP: Response Status
*
Expand Down Expand Up @@ -40,13 +42,13 @@ public interface HttpStatus {
* Describes the optional text "reason" or description for the response status expressed by this record. This value is
* optional.
*/
public val text: HttpString? get() = null
public val text: HttpString?

/**
* Describes the inferred "type" of this HTTP status, based on the response [code] enclosed; this value is optional to
* account for exotic (non-standard) status codes.
*/
public val type: Type? get() = null
public val type: Type?

/**
* ## HTTP Status: Type
Expand Down Expand Up @@ -98,6 +100,19 @@ public interface HttpStatus {
* Describes server error HTTP statuses, which are used to indicate that the server failed to process the client's
* request in some terminal manner. These statuses typically indicate a server-side fault.
*/
SERVER_ERROR(500..599, "Server Error", err = true, serverFault = true),
SERVER_ERROR(500..599, "Server Error", err = true, serverFault = true);

/** Methods for resolving HTTP status code types. */
public companion object {
/**
* Describes the type of HTTP status code based on the numeric [code] value.
*
* @param code The numeric status code to infer the type from.
* @return The inferred type, if any; if the code is exotic or out of spec, `null` is returned.
*/
@JvmStatic public fun fromCode(code: HttpStatusCode): Type? = code.toInt().let { codeValue ->
entries.firstOrNull { codeValue in it.range }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public class CaseInsensitiveHttpString private constructor (

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CharSequence) return false
return when (other) {
is CaseInsensitiveHttpString -> normalized == other.normalized
is String -> normalized.compareTo(other, ignoreCase = true) == 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package elide.http.api

import elide.annotations.API
import elide.http.MutableHttpHeaders

/**
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
package elide.http

import kotlin.test.*
import elide.http.api.HttpHeader
import elide.http.api.HttpHeaderValue
import elide.core.encoding.Encoding
import elide.http.api.*
import elide.http.api.HttpHeaders.HeaderName
import elide.http.api.HttpHeaders.HeaderName.Companion.asHeaderName
import elide.http.api.HttpHeaders.HeaderValue
import elide.http.api.HttpHeaders.HeaderValue.MultiValue
import elide.struct.sortedSetOf

/** Tests for HTTP header containers. */
Expand Down Expand Up @@ -140,6 +141,28 @@ class HttpHeadersTest {
)
}

@Test @Ignore fun testPutExistingMutable() {
val headers = MutableHttpHeaders.of("Accept" to "hello", "Content-Type" to "application/json")
headers["Content-Type"] = "application/xml"
assertEquals("hello", headers["Accept"])
assertEquals("hello", headers[HeaderName.of("Accept")]?.asString)
assertEquals("application/xml", headers["Content-Type"])
assertEquals("application/xml", headers[HeaderName.of("Content-Type")]?.asString)
}

@Test @Ignore fun testPutMultiMutable() {
val headers = MutableHttpHeaders.of("Accept" to "hello", "Content-Type" to "application/json")
headers["Content-Type"] = "application/xml"
assertEquals("hello", headers["Accept"])
assertEquals("hello", headers[HeaderName.of("Accept")]?.asString)
assertEquals("application/xml", headers["Content-Type"])
assertEquals("application/xml", headers[HeaderName.of("Content-Type")]?.asString)
headers.add("Accept", "world")
assertEquals("hello", headers["Accept"])
assertEquals("hello", headers[HeaderName.of("Accept")]?.asString)
assertEquals("world", headers.getAll("Accept").last())
}

@Test fun testContains() {
val headers = HttpHeaders.of("Content-Type" to "application/json")
assertTrue("Content-Type" in headers)
Expand Down Expand Up @@ -235,6 +258,71 @@ class HttpHeadersTest {
assertEquals("gzip,deflate", HeaderValue.multi("gzip", "deflate").toString())
}

@Test fun testHeaderValueSize() {
assertEquals(1, HeaderValue.single("gzip").size)
assertEquals(1, HeaderValue.multi("gzip").size)
assertEquals(2, HeaderValue.multi("gzip", "deflate").size)
assertEquals(3, HeaderValue.multi("gzip", "deflate", "identity").size)
}

@Test fun testHeaderValueMultiMutableAdd() {
val acceptGzip = HeaderValue.multi("gzip", "deflate")
assertNotNull(acceptGzip)
assertEquals("gzip,deflate", acceptGzip.asString)
assertEquals(2, acceptGzip.size)
assertEquals("gzip", acceptGzip.allValues.first())
assertEquals("deflate", acceptGzip.allValues[1])
assertEquals(12, acceptGzip.asString.length) // `gzip`
assertEquals('g', acceptGzip.asString[0]) // `[g]zip`
assertEquals("gz", acceptGzip.asString.subSequence(0, 2)) // `[gz]ip`
val newValue = (acceptGzip as MultiValue).add("identity")
assertNotNull(acceptGzip)
assertEquals("gzip,deflate", acceptGzip.asString)
assertEquals(2, acceptGzip.size)
assertEquals("gzip", acceptGzip.allValues.first())
assertEquals("deflate", acceptGzip.allValues[1])
assertEquals(12, acceptGzip.asString.length) // `gzip`
assertEquals('g', acceptGzip.asString[0]) // `[g]zip`
assertEquals("gz", acceptGzip.asString.subSequence(0, 2)) // `[gz]ip`
assertNotNull(newValue)
assertEquals("gzip,deflate,identity", newValue.asString)
assertEquals(3, newValue.size)
assertEquals("gzip", newValue.allValues.first())
assertEquals("deflate", newValue.allValues[1])
assertEquals("identity", newValue.allValues[2])
assertEquals(21, newValue.asString.length) // `gzip`
assertEquals('g', newValue.asString[0]) // `[g]zip`
assertEquals("gz", newValue.asString.subSequence(0, 2)) // `[gz]ip`
}

@Test fun testHeaderValueMultiMutableRemove() {
val acceptGzip = HeaderValue.multi("gzip", "deflate")
assertNotNull(acceptGzip)
assertEquals("gzip,deflate", acceptGzip.asString)
assertEquals(2, acceptGzip.size)
assertEquals("gzip", acceptGzip.allValues.first())
assertEquals("deflate", acceptGzip.allValues[1])
assertEquals(12, acceptGzip.asString.length) // `gzip`
assertEquals('g', acceptGzip.asString[0]) // `[g]zip`
assertEquals("gz", acceptGzip.asString.subSequence(0, 2)) // `[gz]ip`
val newValue = (acceptGzip as MultiValue).remove("gzip")
assertNotNull(acceptGzip)
assertEquals("gzip,deflate", acceptGzip.asString)
assertEquals(2, acceptGzip.size)
assertEquals("gzip", acceptGzip.allValues.first())
assertEquals("deflate", acceptGzip.allValues[1])
assertEquals(12, acceptGzip.asString.length) // `gzip`
assertEquals('g', acceptGzip.asString[0]) // `[g]zip`
assertEquals("gz", acceptGzip.asString.subSequence(0, 2)) // `[gz]ip`
assertNotNull(newValue)
assertEquals("deflate", newValue.asString)
assertEquals(1, newValue.size)
assertEquals("deflate", newValue.allValues.first())
assertEquals(7, newValue.asString.length) // `deflate`
assertEquals('d', newValue.asString[0]) // `[d]eflate`
assertEquals("de", newValue.asString.subSequence(0, 2)) // `[de]flate`
}

@Test fun testHeaderValueComparableMultiple() {
val left = HeaderValue.multi("gzip", "deflate")
val right = HeaderValue.multi("gzip", "deflate")
Expand Down Expand Up @@ -371,4 +459,45 @@ class HttpHeadersTest {
assertNotNull(factory.of(mapOf("Content-Type" to "application/json")))
assertNotNull(factory.of(listOf("Content-Type" to "application/json")))
}

@Test fun testHeaderValueSingularToken() {
val token = HttpToken("gzip")
assertEquals("gzip", token.toString())
assertEquals("gzip", token.asString)
assertEquals("gzip", token.value)
assertEquals("gzip", token.allValues.first())
}

@Test fun testHeaderValueMultiToken() {
val token = HttpTokenList(listOf("gzip", "deflate"))
assertEquals("gzip,deflate", token.toString())
assertEquals("gzip,deflate", token.asString)
assertEquals("gzip", token.value)
assertEquals("gzip", token.allValues.first())
assertEquals("deflate", token.allValues.last())
}

@Test fun testLanguageToken() {
val token = Language("en")
assertEquals("en", token.toString())
assertEquals("en", token.asString)
assertEquals("en", token.value)
assertEquals("en", token.allValues.first())
}

@Test fun testEncodingToken() {
val token = HttpEncoding("utf-8" to Encoding.UTF_8)
assertEquals("utf-8", token.toString())
assertEquals("utf-8", token.asString)
assertEquals("utf-8", token.value)
assertEquals("utf-8", token.allValues.first())
}

@Test fun testMimetypeToken() {
val token = Mimetype("application/octet-stream")
assertEquals("application/octet-stream", token.toString())
assertEquals("application/octet-stream", token.asString)
assertEquals("application/octet-stream", token.value)
assertEquals("application/octet-stream", token.allValues.first())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2024 Elide Ventures, LLC.
*
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://opensource.org/license/mit/
*
* 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.
*/

package elide.http

import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import elide.http.api.HttpHeaders
import elide.http.api.HttpMessage
import elide.http.api.HttpMessageType
import elide.http.api.HttpPayload
import elide.http.api.HttpVersion
import elide.http.api.MutableHttpHeaders
import elide.http.api.MutableHttpMessage
import elide.http.api.MutableHttpPayload

class HttpMessageTest {
@Test fun testImmutableInterface() {
assertFalse(object: HttpMessage {
override val type: HttpMessageType
get() = error("not implemented")
override val version: HttpVersion
get() = error("not implemented")
override val headers: HttpHeaders
get() = error("not implemented")
override val body: HttpPayload
get() = error("not implemented")
}.mutable)
}

@Test fun testMutableInterface() {
assertTrue(object: MutableHttpMessage {
override val type: HttpMessageType
get() = error("not implemented")
override val version: HttpVersion
get() = error("not implemented")
override val headers: MutableHttpHeaders
get() = error("not implemented")
override val body: MutableHttpPayload
get() = error("not implemented")
}.mutable)
}
}
Loading

0 comments on commit 0d3d6b4

Please sign in to comment.