Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package com.izivia.ocpp.wamp.server.impl
import org.http4k.core.UriTemplate

class OcppWsEndpoint(path: String) {
fun extractChargingStationOcppId(uri: String) =
uriTemplate.extract(uri.replace("""//""", """/"""))["chargingStationOcppId"]
// extract alone searches the template anywhere in the uri, and throws when it is not there
fun extractChargingStationOcppId(uri: String): String? =
uri.replace("""//""", """/""")
.takeIf { uriTemplate.matches(it) }
?.let { runCatching { uriTemplate.extract(it)["chargingStationOcppId"] }.getOrNull() }

val uriTemplate = UriTemplate.from("/$path/{chargingStationOcppId}")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package com.izivia.ocpp.wamp

import com.izivia.ocpp.wamp.server.impl.OcppWsEndpoint
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import strikt.api.expectThat
import strikt.assertions.isEqualTo
import strikt.assertions.isNull

class OcppWsEndPointTest {

Expand All @@ -28,4 +31,26 @@ class OcppWsEndPointTest {

expectThat(chargingStationOcppId).isEqualTo("BOUY_0736")
}

@Test
fun `should Parse with a trailing slash`() {
expectThat(ocppWsEndpoint.extractChargingStationOcppId("/ws/BOUY_0736/")).isEqualTo("BOUY_0736")
}

@ParameterizedTest
@ValueSource(strings = ["/other/OK", "/ws", "/ws/", "/"])
fun `should not find an id in a url outside the endpoint`(uri: String) {
expectThat(ocppWsEndpoint.extractChargingStationOcppId(uri)).isNull()
}

@ParameterizedTest
@ValueSource(strings = ["/ws/OK/extra", "/xws/OK", "/ocpp/ws/OK"])
fun `should not find an id in a url that only contains the endpoint`(uri: String) {
expectThat(ocppWsEndpoint.extractChargingStationOcppId(uri)).isNull()
}

@Test
fun `should not find an id that cannot be decoded`() {
expectThat(ocppWsEndpoint.extractChargingStationOcppId("/ws/%ZZ")).isNull()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.izivia.ocpp.integration.test

import com.izivia.ocpp.core16.ChargePointOperations
import com.izivia.ocpp.integration.ApiFactory
import com.izivia.ocpp.integration.CSMS
import com.izivia.ocpp.integration.model.CSMSSettings
import com.izivia.ocpp.integration.model.ServerSetting
import com.izivia.ocpp.integration.model.TransportEnum
import com.izivia.ocpp.operation.information.ChargingStationConfig
import com.izivia.ocpp.transport.OcppVersion
import io.mockk.mockk
import org.junit.jupiter.api.Test
import strikt.api.expect
import strikt.assertions.isEqualTo
import strikt.assertions.isIn
import strikt.assertions.isNotNull
import java.net.ServerSocket
import java.net.URI
import java.net.http.HttpClient
import java.net.http.WebSocket
import java.net.http.WebSocketHandshakeException
import java.util.concurrent.CompletionException
import java.util.concurrent.TimeUnit
import com.izivia.ocpp.OcppVersion as WampOcppVersion

class WebsocketEndpointPathTest {
private val port = ServerSocket(0).use { it.localPort }
private val clientErrors = 400..499
private val twoPathsOnOnePort = listOf(
websocketSetting("ws16", OcppVersion.OCPP_1_6),
websocketSetting("ws20", OcppVersion.OCPP_2_0)
)

@Test
fun `station connects on each websocket path of a port`() {
val server = csms(twoPathsOnOnePort)
server.start()

try {
expect {
that(handshakeStatus("/ws16/OK", WampOcppVersion.OCPP_1_6)).isEqualTo(101)
that(handshakeStatus("/ws20/OK", WampOcppVersion.OCPP_2_0)).isEqualTo(101)
}
} finally {
server.stop()
}
}

@Test
fun `url outside every websocket path of a port is refused as a client error`() {
val server = csms(twoPathsOnOnePort)
server.start()

try {
expect {
that(handshakeStatus("/other/OK", WampOcppVersion.OCPP_1_6)).isNotNull().isIn(clientErrors)
that(handshakeStatus("/ws16/OK/extra", WampOcppVersion.OCPP_1_6)).isNotNull().isIn(clientErrors)
}
} finally {
server.stop()
}
}

@Test
fun `url outside the websocket path is refused as a client error`() {
val server = csms(listOf(websocketSetting("ws", OcppVersion.OCPP_1_6)))
server.start()

try {
expect {
listOf("/other/OK", "/ws", "/ws/", "/", "/ws/OK/extra", "/ocpp/ws/OK").forEach { path ->
that(handshakeStatus(path, WampOcppVersion.OCPP_1_6))
.describedAs { "status of $path: $this" }
.isNotNull()
.isIn(clientErrors)
}
}
} finally {
server.stop()
}
}

private fun websocketSetting(path: String, version: OcppVersion) = ServerSetting(
port = port,
path = path,
ocppVersion = setOf(version),
transportType = TransportEnum.WEBSOCKET
)

private fun csms(servers: List<ServerSetting>): CSMS =
ApiFactory.csmsOcppServer(
csmsSettings = CSMSSettings(servers),
csmsApiCallbacks = listOf(mockk<ChargePointOperations>(relaxed = true)),
fn = { ChargingStationConfig(acceptConnection = it == "OK", soapUrl = null) }
)

private fun handshakeStatus(path: String, version: WampOcppVersion): Int? =
try {
HttpClient.newHttpClient().newWebSocketBuilder()
.subprotocols(version.subprotocol)
.buildAsync(URI("ws://localhost:$port$path"), object : WebSocket.Listener {})
.orTimeout(5, TimeUnit.SECONDS)
.join()
.abort()
101
} catch (e: CompletionException) {
(e.cause as? WebSocketHandshakeException)?.response?.statusCode()
}
}
Loading