Skip to content

Commit

Permalink
test: introduce basic websocket client test
Browse files Browse the repository at this point in the history
Only for JVM since the websocket testing libraries are not available on other platforms
  • Loading branch information
jimirocks committed Mar 10, 2024
1 parent 4a95565 commit 7b7ade3
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 10 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ kotlin {
jvmTest.dependencies {
implementation(libs.kotest.runner.junit5)
runtimeOnly(libs.slf4j.simple)
implementation(libs.ktor.server.websockets)
implementation(libs.ktor.server.test.host)
}
jsMain.dependencies {
implementation(libs.ktor.client.js)
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "k
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }

kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.2" }

Expand Down
23 changes: 13 additions & 10 deletions src/commonMain/kotlin/ktor/WebsocketLoxoneClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.withTimeout
import kotlin.jvm.JvmOverloads

class WebsocketLoxoneClient(
private val endpoint: LoxoneEndpoint,
class WebsocketLoxoneClient internal constructor(
private val client: HttpClient,
private val endpoint: LoxoneEndpoint? = null,
private val authenticator: LoxoneTokenAuthenticator? = null
) : LoxoneClient {

private val logger = KotlinLogging.logger {}
@JvmOverloads constructor(
endpoint: LoxoneEndpoint,
authenticator: LoxoneTokenAuthenticator? = null
) : this(HttpClient { install(WebSockets) }, endpoint, authenticator)

private val client = HttpClient {
install(WebSockets)
}
private val logger = KotlinLogging.logger {}

private val webSocketSession = AtomicReference<ClientWebSocketSession?>(null)

Expand Down Expand Up @@ -74,11 +77,11 @@ class WebsocketLoxoneClient(
webSocketSession.compareAndSet(
null,
client.webSocketSession(
host = endpoint.host,
port = endpoint.port,
path = endpoint.path + WS_PATH,
host = endpoint?.host,
port = endpoint?.port,
path = if (endpoint != null) endpoint.path + WS_PATH else WS_PATH,
block = {
url.protocol = if (endpoint.useSsl) URLProtocol.WSS else URLProtocol.WS
url.protocol = if (endpoint?.useSsl == true) URLProtocol.WSS else URLProtocol.WS
}
)
)
Expand Down
57 changes: 57 additions & 0 deletions src/jvmTest/kotlin/ktor/WebsocketLoxoneClientTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cz.smarteon.loxone.ktor

import cz.smarteon.loxone.Codec
import cz.smarteon.loxone.message.ApiInfo
import cz.smarteon.loxone.message.MessageHeader
import cz.smarteon.loxone.message.MessageKind.TEXT
import cz.smarteon.loxone.message.TestingLoxValues.API_INFO_MSG_VAL
import cz.smarteon.loxone.message.TestingMessages.okMsg
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.ktor.client.*
import io.ktor.server.application.*
import io.ktor.server.testing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.channels.consumeEach
import io.ktor.client.plugins.websocket.WebSockets as ClientWebsockets
import io.ktor.server.websocket.WebSockets as ServerWebsockets

class WebsocketLoxoneClientTest : StringSpec({

"should call simple command" {
testWebsocket {
val client = WebsocketLoxoneClient(this)

client.callRaw("jdev/cfg/api") shouldBe okMsg("dev/cfg/api", API_INFO_MSG_VAL)

val response = client.call(ApiInfo.command)
response.code shouldBe "200"
response.value shouldBe API_INFO_MSG_VAL
response.control shouldBe "dev/cfg/api"
}
}

})

private suspend fun testWebsocket(test: suspend HttpClient.() -> Unit) = testApplication {
application { install(ServerWebsockets) }

routing {
webSocketRaw(path = "/ws/rfc6455") {
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
when (frame.readText()) {
"jdev/cfg/api" -> {
val text = okMsg("dev/cfg/api", API_INFO_MSG_VAL).encodeToByteArray()
send(Frame.Binary(true, Codec.writeHeader(MessageHeader(TEXT, false, text.size.toLong()))))
send(Frame.Text(true, text))
}
}
}
}
}
}

createClient { install(ClientWebsockets) }.use { it.test() }
}

0 comments on commit 7b7ade3

Please sign in to comment.