Skip to content

Commit

Permalink
Run echo test WS server (and http) as a separate build service
Browse files Browse the repository at this point in the history
  • Loading branch information
joffrey-bion committed Feb 23, 2024
1 parent 27ff310 commit 88322de
Show file tree
Hide file tree
Showing 27 changed files with 330 additions and 184 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
build/

docs/kdoc

karma.config.d
1 change: 1 addition & 0 deletions krossbow-websocket-builtin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("krossbow-multiplatform")
id("krossbow-publish")
id("websocket-test-server")
}

description = "Multiplatform implementation of Krossbow's WebSocket API adapting the platforms' built-in " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.hildan.krossbow.websocket.js

import org.hildan.krossbow.websocket.test.*

class JsWebSocketClientTest : WebSocketClientTestSuite(supportsStatusCodes = false) {
class JsWebSocketClientTest : WebSocketClientTestSuite(supportsStatusCodes = false, supportsCustomHeaders = false) {

override fun provideClient() = BrowserWebSocketClient
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface WebSocketClient {
* optional. Implementations that don't support them must throw an [IllegalArgumentException] if [headers] is not
* empty.
*
* @throws IllegalArgumentException if the headers map is not empty but the client doesn't support custom headers
* @throws WebSocketConnectionException if an error occurs during the connection.
*/
suspend fun connect(url: String, headers: Map<String, String> = emptyMap()): WebSocketConnection
Expand Down
1 change: 1 addition & 0 deletions krossbow-websocket-ktor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("krossbow-multiplatform")
id("krossbow-publish")
alias(libs.plugins.kotlin.atomicfu)
id("websocket-test-server")
}

description = "Multiplatform implementation of Krossbow's WebSocket API using Ktor's web sockets."
Expand Down
1 change: 1 addition & 0 deletions krossbow-websocket-okhttp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("krossbow-jvm")
id("krossbow-publish")
id("websocket-test-server")
}

description = "A Krossbow adapter for OkHttp's WebSocket client"
Expand Down
1 change: 1 addition & 0 deletions krossbow-websocket-spring/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("krossbow-jvm")
id("krossbow-publish")
id("websocket-test-server")
}

description = "A Krossbow adapter for Spring's default WebSocket client and SockJS client"
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.hildan.krossbow.websocket.test

expect fun getTestServerConfig(): TestServerConfig

data class TestServerConfig(
val host: String,
val wsPort: Int,
val httpPort: Int,
) {
val wsUrl: String = "ws://$host:$wsPort"

// we need ws:// scheme because the browser web socket doesn't support anything else
val wsUrlWithHttpPort: String = "ws://$host:$httpPort"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import kotlinx.io.bytestring.*
import org.hildan.krossbow.websocket.*
import kotlin.test.*

abstract class WebSocketClientTestSuite(val supportsStatusCodes: Boolean = true) {

abstract class WebSocketClientTestSuite(
val supportsStatusCodes: Boolean = true,
val supportsCustomHeaders: Boolean = true,
) {
abstract fun provideClient(): WebSocketClient

private lateinit var wsClient: WebSocketClient

companion object {
private val testServerConfig: TestServerConfig = getTestServerConfig()
}

@BeforeTest
fun setupClient() {
wsClient = provideClient()
Expand All @@ -24,20 +30,17 @@ abstract class WebSocketClientTestSuite(val supportsStatusCodes: Boolean = true)
}
}

@IgnoreOnNative
@IgnoreOnJS
@Test
fun testConnectFailure_correctStatusCodeInException() = runSuspendingTest {
runAlongHttpServer { baseUrl ->
assertCorrectStatusReported(baseUrl, 200) // not good for WS, should be 101
assertCorrectStatusReported(baseUrl, 301)
assertCorrectStatusReported(baseUrl, 302)
assertCorrectStatusReported(baseUrl, 401)
assertCorrectStatusReported(baseUrl, 403)
assertCorrectStatusReported(baseUrl, 404)
assertCorrectStatusReported(baseUrl, 500)
assertCorrectStatusReported(baseUrl, 503)
}
val baseUrl = testServerConfig.wsUrlWithHttpPort
assertCorrectStatusReported(baseUrl, 200) // not good for WS, should be 101
assertCorrectStatusReported(baseUrl, 301)
assertCorrectStatusReported(baseUrl, 302)
assertCorrectStatusReported(baseUrl, 401)
assertCorrectStatusReported(baseUrl, 403)
assertCorrectStatusReported(baseUrl, 404)
assertCorrectStatusReported(baseUrl, 500)
assertCorrectStatusReported(baseUrl, 503)
}

private suspend fun assertCorrectStatusReported(baseUrl: String, statusCodeToTest: Int) {
Expand All @@ -53,23 +56,26 @@ abstract class WebSocketClientTestSuite(val supportsStatusCodes: Boolean = true)
}
}

@IgnoreOnNative
@IgnoreOnJS
@Test
fun testHandshakeHeaders() = runSuspendingTestWithEchoServer { server ->
val session = wsClient.connect(
url = server.localUrl,
headers = mapOf("My-Header-1" to "my-value-1", "My-Header-2" to "my-value-2"),
)
val header = session.expectTextFrame("header info frame")
assertEquals("custom-headers:My-Header-1=my-value-1, My-Header-2=my-value-2", header.text)
fun testHandshakeHeaders() = runSuspendingTest {
val connectWithTestHeaders = suspend {
wsClient.connect(
url = testServerConfig.wsUrl,
headers = mapOf("My-Header-1" to "my-value-1", "My-Header-2" to "my-value-2"),
)
}
if (supportsCustomHeaders) {
val session = connectWithTestHeaders()
val header = session.expectTextFrame("header info frame")
assertEquals("custom-headers:My-Header-1=my-value-1, My-Header-2=my-value-2", header.text)
} else {
assertFailsWith<IllegalArgumentException> { connectWithTestHeaders() }
}
}

@IgnoreOnNative
@IgnoreOnJS
@Test
fun testEchoText() = runSuspendingTestWithEchoServer { server ->
val session = wsClient.connect(server.localUrl)
fun testEchoText() = runSuspendingTest {
val session = wsClient.connect(testServerConfig.wsUrl)

session.sendText("hello")
val helloResponse = session.expectTextFrame("hello frame")
Expand All @@ -81,11 +87,9 @@ abstract class WebSocketClientTestSuite(val supportsStatusCodes: Boolean = true)
session.expectNoMoreFrames("after echo text CLOSE frame")
}

@IgnoreOnNative
@IgnoreOnJS
@Test
fun testEchoBinary() = runSuspendingTestWithEchoServer { server ->
val session = wsClient.connect(server.localUrl)
fun testEchoBinary() = runSuspendingTest {
val session = wsClient.connect(testServerConfig.wsUrl)

val fortyTwos = ByteString(42, 42, 42)
session.sendBinary(fortyTwos)
Expand All @@ -97,27 +101,4 @@ abstract class WebSocketClientTestSuite(val supportsStatusCodes: Boolean = true)
session.expectCloseFrame("after echo binary")
session.expectNoMoreFrames("after echo binary CLOSE frame")
}

@IgnoreOnNative
@IgnoreOnJS
@Test
fun testClose() = runSuspendingTestWithEchoServer { server ->
val session = wsClient.connect(server.localUrl)

server.close()

session.expectCloseFrame("after connect")
session.expectNoMoreFrames("after CLOSE frame following connect")
}
}

private fun runSuspendingTestWithEchoServer(block: suspend (server: TestServer) -> Unit) {
runSuspendingTest {
runAlongEchoWSServer { server ->
block(server)
}
}
}

private val TestServer.localUrl: String
get() = "ws://localhost:$port"

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.hildan.krossbow.websocket.test

actual fun getTestServerConfig(): TestServerConfig = when (isBrowser()) {
true -> getTestServerConfigBrowser()
false -> getTestServerConfigFromEnv()
}

// This variable is defined using the webpack DefinePlugin in karma.config.d/<somename>.js
// which is itself generated from Gradle in the test-server plugin
private fun getTestServerConfigBrowser(): TestServerConfig =
js("testServerConfig").unsafeCast<AutobahnConfigJson>().toCommonConfig()

private external interface AutobahnConfigJson {
val host: String
val wsPort: Int
val httpPort: Int
}

private fun AutobahnConfigJson.toCommonConfig() = TestServerConfig(
host = host,
wsPort = wsPort,
httpPort = httpPort,
)

private fun getTestServerConfigFromEnv(): TestServerConfig = TestServerConfig(
host = getMandatoryEnvVar("TEST_SERVER_HOST"),
wsPort = getMandatoryEnvVar("TEST_SERVER_WS_PORT").toInt(),
httpPort = getMandatoryEnvVar("TEST_SERVER_HTTP_PORT").toInt(),
)

private fun getMandatoryEnvVar(varName: String): String =
process.env[varName] ?: error("Environment variable $varName not provided")

external val process: Process

external interface Process {
val env: dynamic
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.hildan.krossbow.websocket.test

actual fun getTestServerConfig() = TestServerConfig(
host = getMandatoryEnvVar("TEST_SERVER_HOST"),
wsPort = getMandatoryEnvVar("TEST_SERVER_WS_PORT").toInt(),
httpPort = getMandatoryEnvVar("TEST_SERVER_HTTP_PORT").toInt(),
)

private fun getMandatoryEnvVar(varName: String): String =
System.getenv(varName) ?: error("Environment variable $varName not provided")

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.hildan.krossbow.websocket.test

import kotlinx.cinterop.*
import platform.posix.*

actual fun getTestServerConfig() = TestServerConfig(
host = getMandatoryEnvVar("TEST_SERVER_HOST"),
wsPort = getMandatoryEnvVar("TEST_SERVER_WS_PORT").toInt(),
httpPort = getMandatoryEnvVar("TEST_SERVER_HTTP_PORT").toInt(),
)

@OptIn(ExperimentalForeignApi::class)
private fun getMandatoryEnvVar(varName: String): String = getenv(varName)?.toKString()
?: error("Environment variable $varName not provided")
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencyResolutionManagement {
}

includeBuild("gradle/plugins")
includeBuild("test-server")
include("krossbow-io")
include("krossbow-stomp-core")
include("krossbow-stomp-kxserialization")
Expand Down
16 changes: 16 additions & 0 deletions test-server/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
rootProject.name = "test-server"

dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

include("websocket-test-server")
include("websocket-test-server-gradle-plugin")
Loading

0 comments on commit 88322de

Please sign in to comment.