diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml
index 6b2a2b5d..07f69d89 100644
--- a/.github/actions/run-tests/action.yml
+++ b/.github/actions/run-tests/action.yml
@@ -14,19 +14,14 @@ inputs:
kotest-tag:
description: >
The Kotest Tag to use when executing the tests. Check Tag.kt for valid options. Different Tags might require the
- Databroker to be started in a different mode. Currently only unsecured mode (no tls, no authentication) is
- supported.
- default: '!CustomDatabroker'
+ Databroker to be started in a different mode.
+ default: ''
runs:
using: "composite"
steps:
- - name: "Run Docker Container of Databroker in detached mode"
- run: docker run --pull=always --rm --publish 55556:55556/tcp --detach --name databroker ghcr.io/eclipse/kuksa.val/databroker:${{ inputs.databroker-version }} --port 55556 --insecure
- shell: bash
-
- name: Run 'test' with Gradle Wrapper
- run: ./gradlew test -Dkotest.tags="${{ inputs.kotest-tag}}"
+ run: ./gradlew test -Ddatabroker.tag="${{ inputs.databroker-version }}" -Dkotest.tags="${{ inputs.kotest-tag}}"
shell: bash
- name: Upload Test Reports
@@ -51,8 +46,3 @@ runs:
path: ${{ github.workspace }}/build/reports/jacoco/jacocoRootReport/html/*
if-no-files-found: error
retention-days: 14
-
- - name: "Stop Docker Container of Databroker"
- if: always()
- run: docker stop databroker
- shell: bash
diff --git a/.github/workflows/daily_integration_main-master.yaml b/.github/workflows/daily_integration_main-master.yaml
index e2edaf51..e6cf1179 100644
--- a/.github/workflows/daily_integration_main-master.yaml
+++ b/.github/workflows/daily_integration_main-master.yaml
@@ -21,4 +21,4 @@ jobs:
with:
upload-test-reports: true
databroker-version: master
- kotest-tag: "Integration & DefaultDatabroker"
+ kotest-tag: "Integration"
diff --git a/.run/kuksa-vss-core_UnitTests.run.xml b/.run/All Tests.run.xml
similarity index 82%
rename from .run/kuksa-vss-core_UnitTests.run.xml
rename to .run/All Tests.run.xml
index e5fe1eeb..4ac3a4be 100644
--- a/.run/kuksa-vss-core_UnitTests.run.xml
+++ b/.run/All Tests.run.xml
@@ -1,5 +1,5 @@
-
+
@@ -10,7 +10,7 @@
@@ -18,6 +18,7 @@
truetruefalse
+ false
\ No newline at end of file
diff --git a/.run/kuksa-sdk_UnitTests.run.xml b/.run/Functional Tests.run.xml
similarity index 74%
rename from .run/kuksa-sdk_UnitTests.run.xml
rename to .run/Functional Tests.run.xml
index 5edc8246..4bbbccaa 100644
--- a/.run/kuksa-sdk_UnitTests.run.xml
+++ b/.run/Functional Tests.run.xml
@@ -1,5 +1,5 @@
-
+
@@ -10,14 +10,15 @@
-
+
-
+
truetruefalse
+ false
\ No newline at end of file
diff --git a/.run/kuksa-sdk_IntegrationTests.run.xml b/.run/Integration Tests.run.xml
similarity index 79%
rename from .run/kuksa-sdk_IntegrationTests.run.xml
rename to .run/Integration Tests.run.xml
index 12bc49be..69d563d5 100644
--- a/.run/kuksa-sdk_IntegrationTests.run.xml
+++ b/.run/Integration Tests.run.xml
@@ -1,5 +1,5 @@
-
+
@@ -10,10 +10,10 @@
-
+
-
+
falsetrue
diff --git a/.run/All UnitTests.run.xml b/.run/Unit Tests.run.xml
similarity index 86%
rename from .run/All UnitTests.run.xml
rename to .run/Unit Tests.run.xml
index db91b812..4878386c 100644
--- a/.run/All UnitTests.run.xml
+++ b/.run/Unit Tests.run.xml
@@ -1,5 +1,5 @@
-
+
@@ -18,6 +18,7 @@
truetruefalse
+ false
\ No newline at end of file
diff --git a/.run/app_UnitTests.run.xml b/.run/app_UnitTests.run.xml
deleted file mode 100644
index cfbef6e1..00000000
--- a/.run/app_UnitTests.run.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
- true
- false
-
-
-
\ No newline at end of file
diff --git a/.run/kuksa-vss-processor_UnitTests.run.xml b/.run/kuksa-vss-processor_UnitTests.run.xml
deleted file mode 100644
index a38b517a..00000000
--- a/.run/kuksa-vss-processor_UnitTests.run.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
- false
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index b559da66..bad684eb 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
[![Gitter](https://img.shields.io/gitter/room/kuksa-val/community)](https://gitter.im/kuksa-val/community)
-![SDK:main <-> Databroker:master](https://github.com/eclipse-kuksa/kuksa-android-sdk/actions/workflows/daily_integration_main-master.yaml/badge.svg)
+[![SDK:main <-> Databroker:master](https://github.com/eclipse-kuksa/kuksa-android-sdk/actions/workflows/daily_integration_main-master.yaml/badge.svg)](https://github.com/eclipse-kuksa/kuksa-android-sdk/actions/workflows/daily_integration_main-master.yaml?query=branch%3Amain)
This is an Android SDK for the [KUKSA Vehicle Abstraction Layer](https://github.com/eclipse/kuksa.val).
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index c8d4ad0f..9af15d3a 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -103,7 +103,7 @@ If this build fails it should be considered as a warning
**SDK:main -> Databroker:master**
-![SDK:main <-> Databroker:master](https://github.com/eclipse-kuksa/kuksa-android-sdk/actions/workflows/daily_integration_main-master.yaml/badge.svg)
+[![SDK:main <-> Databroker:master](https://github.com/eclipse-kuksa/kuksa-android-sdk/actions/workflows/daily_integration_main-master.yaml/badge.svg)](https://github.com/eclipse-kuksa/kuksa-android-sdk/actions/workflows/daily_integration_main-master.yaml?query=branch%3Amain)
This means both the SDK and Databroker are running in a kind of "bleeding edge" state in their currently developed version.
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index eb16ec18..dc73e24b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,6 +5,7 @@ detekt = "1.23.5"
datastore = "1.0.0"
constraintlayoutCompose = "1.0.1"
datastorePreferences = "1.0.0"
+dockerJavaCore = "3.3.6"
dokka = "1.9.10"
gson = "2.10.1"
kotlin = "1.9.22"
@@ -34,6 +35,8 @@ androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "d
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
androidx-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "runtimeLivedata" }
+docker-java-core = { module = "com.github.docker-java:docker-java-core", version.ref = "dockerJavaCore" }
+docker-java-transport-httpclient5 = { module = "com.github.docker-java:docker-java-transport-httpclient5", version.ref = "dockerJavaCore" }
grpc-okhttp = { group = "io.grpc", name = "grpc-okhttp", version.ref = "grpc" }
grpc-protobuf = { group = "io.grpc", name = "grpc-protobuf-lite", version.ref = "grpc" }
grpc-stub = { group = "io.grpc", name = "grpc-stub", version.ref = "grpc" }
diff --git a/kuksa-sdk/build.gradle.kts b/kuksa-sdk/build.gradle.kts
index be14acbb..0289305a 100644
--- a/kuksa-sdk/build.gradle.kts
+++ b/kuksa-sdk/build.gradle.kts
@@ -94,6 +94,9 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotest)
testImplementation(libs.mockk)
+
+ testImplementation(libs.docker.java.core)
+ testImplementation(libs.docker.java.transport.httpclient5)
}
publish {
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/DataBrokerConnectorAuthenticationTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/DataBrokerConnectorAuthenticationTest.kt
index cfa5e847..232d291a 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/DataBrokerConnectorAuthenticationTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/DataBrokerConnectorAuthenticationTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,22 +14,32 @@
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
+ *
*/
package org.eclipse.kuksa.connectivity.authentication
+import io.grpc.StatusRuntimeException
+import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
+import io.kotest.matchers.string.shouldContain
+import io.kotest.matchers.types.instanceOf
import org.eclipse.kuksa.connectivity.databroker.DataBrokerConnectorProvider
+import org.eclipse.kuksa.connectivity.databroker.DataBrokerException
+import org.eclipse.kuksa.connectivity.databroker.docker.DataBrokerDockerContainer
+import org.eclipse.kuksa.connectivity.databroker.docker.SecureDataBrokerDockerContainer
import org.eclipse.kuksa.connectivity.databroker.request.FetchRequest
+import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest
import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest
+import org.eclipse.kuksa.mocking.FriendlyVssPathListener
import org.eclipse.kuksa.proto.v1.Types
-import org.eclipse.kuksa.test.TestResourceFile
import org.eclipse.kuksa.test.kotest.Authentication
-import org.eclipse.kuksa.test.kotest.CustomDatabroker
-import org.eclipse.kuksa.test.kotest.Insecure
import org.eclipse.kuksa.test.kotest.Integration
-import java.io.InputStream
+import org.eclipse.kuksa.test.kotest.Secure
+import org.eclipse.kuksa.test.kotest.SecureDataBroker
+import org.eclipse.kuksa.test.kotest.eventuallyConfiguration
import kotlin.random.Random
import kotlin.random.nextInt
@@ -38,7 +48,19 @@ import kotlin.random.nextInt
// ./gradlew clean test -Dkotest.tags="Authentication"
class DataBrokerConnectorAuthenticationTest : BehaviorSpec({
- tags(Integration, Authentication, Insecure, CustomDatabroker)
+ tags(Integration, Authentication, Secure, SecureDataBroker)
+
+ var databrokerContainer: DataBrokerDockerContainer? = null
+ beforeSpec {
+ databrokerContainer = SecureDataBrokerDockerContainer()
+ .apply {
+ start()
+ }
+ }
+
+ afterSpec {
+ databrokerContainer?.stop()
+ }
val random = Random(System.nanoTime())
@@ -46,9 +68,12 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
val speedVssPath = "Vehicle.Speed"
- and("an insecure DataBrokerConnector with a READ_WRITE_ALL JWT") {
- val jwtFileStream = JwtType.READ_WRITE_ALL.asInputStream()
- val dataBrokerConnector = dataBrokerConnectorProvider.createInsecure(jwtFileStream = jwtFileStream)
+ and("a secure DataBrokerConnector with a READ_WRITE_ALL JWT") {
+ val jwtFile = JwtType.READ_WRITE_ALL
+
+ val dataBrokerConnector = dataBrokerConnectorProvider.createSecure(
+ jwtFileStream = jwtFile.asInputStream(),
+ )
and("a successfully established connection") {
val connection = dataBrokerConnector.connect()
@@ -76,9 +101,11 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({
}
}
- and("an insecure DataBrokerConnector with a READ_ALL JWT") {
- val jwtFileStream = JwtType.READ_ALL.asInputStream()
- val dataBrokerConnector = dataBrokerConnectorProvider.createInsecure(jwtFileStream = jwtFileStream)
+ and("a secure DataBrokerConnector with a READ_ALL JWT") {
+ val jwtFile = JwtType.READ_ALL
+ val dataBrokerConnector = dataBrokerConnectorProvider.createSecure(
+ jwtFileStream = jwtFile.asInputStream(),
+ )
and("a successfully established connection") {
val connection = dataBrokerConnector.connect()
@@ -105,9 +132,11 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({
}
}
- and("an insecure DataBrokerConnector with a READ_WRITE_ALL_VALUES_ONLY JWT") {
- val jwtFileStream = JwtType.READ_WRITE_ALL_VALUES_ONLY.asInputStream()
- val dataBrokerConnector = dataBrokerConnectorProvider.createInsecure(jwtFileStream = jwtFileStream)
+ and("a secure DataBrokerConnector with a READ_WRITE_ALL_VALUES_ONLY JWT") {
+ val jwtFile = JwtType.READ_WRITE_ALL_VALUES_ONLY
+ val dataBrokerConnector = dataBrokerConnectorProvider.createSecure(
+ jwtFileStream = jwtFile.asInputStream(),
+ )
and("a successfully established connection") {
val connection = dataBrokerConnector.connect()
@@ -157,20 +186,71 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({
}
}
}
- }
-})
-// The tokens provided here might need to be updated irregularly
-// see: https://github.com/eclipse/kuksa.val/tree/master/jwt
-// The tokens only work when the Databroker is started using the correct public key: jwt.key.pub
-enum class JwtType(private val fileName: String) {
- READ_WRITE_ALL("actuate-provide-all.token"), // ACTUATOR_TARGET and VALUE
- READ_WRITE_ALL_VALUES_ONLY("provide-all.token"), // VALUE
- READ_ALL("read-all.token"),
- ;
-
- fun asInputStream(): InputStream {
- val resourceFile = TestResourceFile(fileName)
- return resourceFile.inputStream()
+ and("a secure DataBrokerConnector with no JWT") {
+ val dataBrokerConnector = dataBrokerConnectorProvider.createSecure(
+ jwtFileStream = null,
+ )
+
+ `when`("Trying to connect") {
+ val result = runCatching {
+ dataBrokerConnector.connect()
+ }
+
+ then("The connection should be successful") {
+ result.getOrNull() shouldNotBe null
+ }
+
+ val connection = result.getOrNull()!!
+
+ `when`("Reading the VALUE of Vehicle.Speed") {
+ val fetchRequest = FetchRequest(speedVssPath)
+ val fetchResult = runCatching {
+ connection.fetch(fetchRequest)
+ }
+
+ then("An error should occur") {
+ val exception = fetchResult.exceptionOrNull()
+ exception shouldNotBe null
+ exception shouldBe instanceOf(DataBrokerException::class)
+ exception!!.message shouldContain "UNAUTHENTICATED"
+ }
+ }
+
+ `when`("Writing the VALUE of Vehicle.Speed") {
+ val nextFloat = random.nextFloat() * 100F
+ val datapoint = Types.Datapoint.newBuilder().setFloat(nextFloat).build()
+ val updateRequest = UpdateRequest(speedVssPath, datapoint)
+
+ val updateResult = runCatching {
+ connection.update(updateRequest)
+ }
+
+ then("An error should occur") {
+ val exception = updateResult.exceptionOrNull()
+ exception shouldNotBe null
+ exception shouldBe instanceOf(DataBrokerException::class)
+ exception!!.message shouldContain "UNAUTHENTICATED"
+ }
+ }
+
+ `when`("Subscribing to the VALUE of Vehicle.Speed") {
+ val subscribeRequest = SubscribeRequest(speedVssPath)
+ val vssPathListener = FriendlyVssPathListener()
+
+ connection.subscribe(subscribeRequest, vssPathListener)
+
+ then("An error should occur") {
+ eventually(eventuallyConfiguration) {
+ vssPathListener.errors.size shouldBe 1
+
+ val exception = vssPathListener.errors.first()
+ exception shouldBe instanceOf(StatusRuntimeException::class)
+ exception.message shouldContain "UNAUTHENTICATED"
+ }
+ }
+ }
+ }
+ }
}
-}
+})
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/JwtType.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/JwtType.kt
new file mode 100644
index 00000000..e87f9d65
--- /dev/null
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/authentication/JwtType.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.eclipse.kuksa.connectivity.authentication
+
+import org.eclipse.kuksa.test.TestResourceFile
+import java.io.InputStream
+
+// The tokens provided here might need to be updated irregularly
+// see: https://github.com/eclipse/kuksa.val/tree/master/jwt
+// The tokens only work when the Databroker is started using the correct public key: jwt.key.pub
+enum class JwtType(private val fileName: String) {
+ READ_WRITE_ALL("authentication/actuate-provide-all.token"), // ACTUATOR_TARGET and VALUE
+ READ_WRITE_ALL_VALUES_ONLY("authentication/provide-all.token"), // VALUE
+ READ_ALL("authentication/read-all.token"),
+ ;
+
+ fun asInputStream(): InputStream {
+ val resourceFile = TestResourceFile(fileName)
+ return resourceFile.inputStream()
+ }
+}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConfig.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConfig.kt
index 4c754649..eafe5055 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConfig.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConfig.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,17 +14,14 @@
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
+ *
*/
package org.eclipse.kuksa.connectivity.databroker
import java.util.concurrent.TimeUnit
-object DataBrokerConfig {
- const val HOST = "127.0.0.1"
- const val PORT = 55556
+const val DATABROKER_HOST = "127.0.0.1"
- // low timeout should be okay, since we are testing against a local service
- const val TIMEOUT_SECONDS = 3L
- val TIMEOUT_UNIT = TimeUnit.SECONDS
-}
+const val DATABROKER_TIMEOUT_SECONDS = 5L
+val DATABROKER_TIMEOUT_UNIT = TimeUnit.SECONDS
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectionTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectionTest.kt
index b4644d32..78e5f4ff 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectionTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectionTest.kt
@@ -24,78 +24,91 @@ import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.string.shouldContain
-import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
-import io.mockk.slot
import io.mockk.verify
-import kotlinx.coroutines.runBlocking
+import org.eclipse.kuksa.connectivity.databroker.docker.DataBrokerDockerContainer
+import org.eclipse.kuksa.connectivity.databroker.docker.InsecureDataBrokerDockerContainer
import org.eclipse.kuksa.connectivity.databroker.listener.DisconnectListener
-import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener
import org.eclipse.kuksa.connectivity.databroker.request.FetchRequest
import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest
import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest
import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest
import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest
import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest
+import org.eclipse.kuksa.extensions.updateRandomFloatValue
import org.eclipse.kuksa.mocking.FriendlyVssNodeListener
-import org.eclipse.kuksa.proto.v1.KuksaValV1
+import org.eclipse.kuksa.mocking.FriendlyVssPathListener
import org.eclipse.kuksa.proto.v1.Types
-import org.eclipse.kuksa.test.kotest.DefaultDatabroker
+import org.eclipse.kuksa.test.extension.equals
+import org.eclipse.kuksa.test.kotest.Insecure
+import org.eclipse.kuksa.test.kotest.InsecureDataBroker
import org.eclipse.kuksa.test.kotest.Integration
+import org.eclipse.kuksa.test.kotest.eventuallyConfiguration
import org.eclipse.kuksa.vssNode.VssDriver
import org.junit.jupiter.api.Assertions
import kotlin.random.Random
-import kotlin.time.Duration.Companion.seconds
class DataBrokerConnectionTest : BehaviorSpec({
- tags(Integration, DefaultDatabroker)
+ tags(Integration, Insecure, InsecureDataBroker)
+
+ var databrokerContainer: DataBrokerDockerContainer? = null
+ beforeSpec {
+ databrokerContainer = InsecureDataBrokerDockerContainer()
+ .apply {
+ start()
+ }
+ }
+
+ afterSpec {
+ databrokerContainer?.stop()
+ }
given("A successfully established connection to the DataBroker") {
- val dataBrokerConnection = connectToDataBrokerBlocking()
+ val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
+ val connector = dataBrokerConnectorProvider.createInsecure()
+ val dataBrokerConnection = connector.connect()
+
+ val dataBrokerTransporter = DataBrokerTransporter(dataBrokerConnectorProvider.managedChannel)
and("A request with a valid VSS Path") {
val vssPath = "Vehicle.Acceleration.Lateral"
val field = Types.Field.FIELD_VALUE
- val subscribeRequest = SubscribeRequest(vssPath, field)
+ val initialValue = dataBrokerTransporter.updateRandomFloatValue(vssPath)
+
+ val subscribeRequest = SubscribeRequest(vssPath, field)
`when`("Subscribing to the VSS path") {
- val vssPathListener = mockk(relaxed = true)
+ val vssPathListener = FriendlyVssPathListener()
dataBrokerConnection.subscribe(subscribeRequest, vssPathListener)
then("The #onEntryChanged method is triggered") {
- val capturingSlot = slot>()
- verify(timeout = 100L) {
- vssPathListener.onEntryChanged(capture(capturingSlot))
+ eventually(eventuallyConfiguration) {
+ vssPathListener.updates.flatten().count {
+ val entry = it.entry
+ val value = entry.value
+ entry.path == vssPath && value.float.equals(initialValue, 0.0001f)
+ } shouldBe 1
}
-
- val entryUpdates = capturingSlot.captured
- entryUpdates.size shouldBe 1
- entryUpdates[0].entry.path shouldBe vssPath
}
`when`("The observed VSS path changes") {
- clearMocks(vssPathListener)
+ vssPathListener.reset()
val random = Random(System.currentTimeMillis())
- val newValue = random.nextFloat()
- val datapoint = Types.Datapoint.newBuilder().setFloat(newValue).build()
+ val updatedValue = random.nextFloat()
+ val datapoint = Types.Datapoint.newBuilder().setFloat(updatedValue).build()
val updateRequest = UpdateRequest(vssPath, datapoint, field)
dataBrokerConnection.update(updateRequest)
then("The #onEntryChanged callback is triggered with the new value") {
- val capturingSlot = slot>()
-
- verify(timeout = 100) {
- vssPathListener.onEntryChanged(capture(capturingSlot))
+ eventually(eventuallyConfiguration) {
+ vssPathListener.updates.flatten().count {
+ val entry = it.entry
+ val value = entry.value
+ entry.path == vssPath && value.float.equals(updatedValue, 0.0001f)
+ } shouldBe 1
}
-
- val entryUpdates = capturingSlot.captured
- val capturedDatapoint = entryUpdates[0].entry.value
- val float = capturedDatapoint.float
-
- Assertions.assertEquals(newValue, float, 0.0001f)
}
}
}
@@ -193,7 +206,7 @@ class DataBrokerConnectionTest : BehaviorSpec({
dataBrokerConnection.subscribe(subscribeRequest, listener = vssNodeListener)
then("The #onNodeChanged method is triggered") {
- eventually(1.seconds) {
+ eventually(eventuallyConfiguration) {
vssNodeListener.updatedVssNodes.size shouldBe 1
}
}
@@ -206,14 +219,12 @@ class DataBrokerConnectionTest : BehaviorSpec({
dataBrokerConnection.update(updateRequest)
then("Every child node has been updated with the correct value") {
- eventually(1.seconds) {
- vssNodeListener.updatedVssNodes.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ vssNodeListener.updatedVssNodes.count {
+ val heartRate = it.heartRate
+ heartRate.value == newHeartRateValue
+ } shouldBe 1
}
-
- val updatedDriver = vssNodeListener.updatedVssNodes.last()
- val heartRate = updatedDriver.heartRate
-
- heartRate.value shouldBe newHeartRateValue
}
}
@@ -225,14 +236,12 @@ class DataBrokerConnectionTest : BehaviorSpec({
dataBrokerConnection.update(updateRequest)
then("The subscribed vssNode should be updated") {
- eventually(1.seconds) {
- vssNodeListener.updatedVssNodes.size shouldBe 3
+ eventually(eventuallyConfiguration) {
+ vssNodeListener.updatedVssNodes.count {
+ val heartRate = it.heartRate
+ heartRate.value == newHeartRateValue
+ } shouldBe 1
}
-
- val updatedDriver = vssNodeListener.updatedVssNodes.last()
- val heartRate = updatedDriver.heartRate
-
- heartRate.value shouldBe newHeartRateValue
}
}
}
@@ -242,15 +251,16 @@ class DataBrokerConnectionTest : BehaviorSpec({
val invalidVssPath = "Vehicle.Some.Unknown.Path"
`when`("Trying to subscribe to the INVALID VSS path") {
- val vssPathListener = mockk(relaxed = true)
+ val vssPathListener = FriendlyVssPathListener()
val subscribeRequest = SubscribeRequest(invalidVssPath)
dataBrokerConnection.subscribe(subscribeRequest, vssPathListener)
then("The VssPathListener#onError method should be triggered with 'NOT_FOUND' (Path not found)") {
- val capturingSlot = slot()
- verify(timeout = 100L) { vssPathListener.onError(capture(capturingSlot)) }
- val capturedThrowable = capturingSlot.captured
- capturedThrowable.message shouldContain "NOT_FOUND"
+ eventually(eventuallyConfiguration) {
+ vssPathListener.errors.count {
+ it.message?.contains("NOT_FOUND") == true
+ } shouldBe 1
+ }
}
}
@@ -331,20 +341,3 @@ private fun createRandomIntDatapoint(): Types.Datapoint {
val newValue = random.nextInt()
return Types.Datapoint.newBuilder().setInt32(newValue).build()
}
-
-private fun connectToDataBrokerBlocking(): DataBrokerConnection {
- var connection: DataBrokerConnection
-
- runBlocking {
- val connector = DataBrokerConnectorProvider().createInsecure()
- try {
- connection = connector.connect()
- } catch (ignored: DataBrokerException) {
- val errorMessage = "Could not establish a connection to the DataBroker. " +
- "Check if the DataBroker is running and correctly configured in DataBrokerConfig."
- throw IllegalStateException(errorMessage)
- }
- }
-
- return connection
-}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorProvider.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorProvider.kt
index 53df36bb..e7c39ad4 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorProvider.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorProvider.kt
@@ -24,15 +24,17 @@ import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import io.grpc.TlsChannelCredentials
import org.eclipse.kuksa.connectivity.authentication.JsonWebToken
+import org.eclipse.kuksa.connectivity.authentication.JwtType
import org.eclipse.kuksa.model.TimeoutConfig
+import org.eclipse.kuksa.test.TestResourceFile
import java.io.IOException
import java.io.InputStream
class DataBrokerConnectorProvider {
lateinit var managedChannel: ManagedChannel
fun createInsecure(
- host: String = DataBrokerConfig.HOST,
- port: Int = DataBrokerConfig.PORT,
+ host: String = DATABROKER_HOST,
+ port: Int = DATABROKER_PORT,
jwtFileStream: InputStream? = null,
): DataBrokerConnector {
managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()
@@ -46,16 +48,16 @@ class DataBrokerConnectorProvider {
managedChannel,
jsonWebToken,
).apply {
- timeoutConfig = TimeoutConfig(DataBrokerConfig.TIMEOUT_SECONDS, DataBrokerConfig.TIMEOUT_UNIT)
+ timeoutConfig = TimeoutConfig(DATABROKER_TIMEOUT_SECONDS, DATABROKER_TIMEOUT_UNIT)
}
}
fun createSecure(
- host: String = DataBrokerConfig.HOST,
- port: Int = DataBrokerConfig.PORT,
- rootCertFileStream: InputStream,
+ host: String = DATABROKER_HOST,
+ port: Int = DATABROKER_PORT,
overrideAuthority: String = "",
- jwtFileStream: InputStream? = null,
+ rootCertFileStream: InputStream = TestResourceFile("tls/CA.pem").inputStream(),
+ jwtFileStream: InputStream? = JwtType.READ_WRITE_ALL.asInputStream(),
): DataBrokerConnector {
val tlsCredentials: ChannelCredentials
try {
@@ -86,7 +88,7 @@ class DataBrokerConnectorProvider {
managedChannel,
jsonWebToken,
).apply {
- timeoutConfig = TimeoutConfig(DataBrokerConfig.TIMEOUT_SECONDS, DataBrokerConfig.TIMEOUT_UNIT)
+ timeoutConfig = TimeoutConfig(DATABROKER_TIMEOUT_SECONDS, DATABROKER_TIMEOUT_UNIT)
}
}
}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorSecureTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorSecureTest.kt
index 0ff03f1f..02b60192 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorSecureTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorSecureTest.kt
@@ -19,23 +19,40 @@
package org.eclipse.kuksa.connectivity.databroker
import io.kotest.core.spec.style.BehaviorSpec
+import org.eclipse.kuksa.connectivity.databroker.docker.DataBrokerDockerContainer
+import org.eclipse.kuksa.connectivity.databroker.docker.SecureDataBrokerDockerContainer
import org.eclipse.kuksa.test.TestResourceFile
-import org.eclipse.kuksa.test.kotest.CustomDatabroker
import org.eclipse.kuksa.test.kotest.Integration
import org.eclipse.kuksa.test.kotest.Secure
+import org.eclipse.kuksa.test.kotest.SecureDataBroker
+import org.eclipse.kuksa.test.kotest.Tls
import org.junit.jupiter.api.Assertions
// run command: ./gradlew clean test -Dkotest.tags="Secure"
class DataBrokerConnectorSecureTest : BehaviorSpec({
- tags(Integration, Secure, CustomDatabroker)
+ tags(Integration, Secure, Tls, SecureDataBroker)
+
+ var databrokerContainer: DataBrokerDockerContainer? = null
+ beforeSpec {
+ databrokerContainer = SecureDataBrokerDockerContainer()
+ .apply {
+ start()
+ }
+ }
+
+ afterSpec {
+ databrokerContainer?.stop()
+ }
given("A DataBrokerConnectorProvider") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
and("a secure DataBrokerConnector with valid Host, Port and TLS certificate") {
- val certificate = TestResourceFile("CA.pem")
- val dataBrokerConnector =
- dataBrokerConnectorProvider.createSecure(rootCertFileStream = certificate.inputStream())
+ val tlsCertificate = TestResourceFile("tls/CA.pem")
+
+ val dataBrokerConnector = dataBrokerConnectorProvider.createSecure(
+ rootCertFileStream = tlsCertificate.inputStream(),
+ )
`when`("Trying to establish a secure connection") {
val connection = dataBrokerConnector.connect()
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorTest.kt
index 63e30827..82ce9e59 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorTest.kt
@@ -21,12 +21,26 @@ package org.eclipse.kuksa.connectivity.databroker
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldNotBe
-import org.eclipse.kuksa.test.kotest.DefaultDatabroker
+import org.eclipse.kuksa.connectivity.databroker.docker.DataBrokerDockerContainer
+import org.eclipse.kuksa.connectivity.databroker.docker.InsecureDataBrokerDockerContainer
import org.eclipse.kuksa.test.kotest.Insecure
+import org.eclipse.kuksa.test.kotest.InsecureDataBroker
import org.eclipse.kuksa.test.kotest.Integration
class DataBrokerConnectorTest : BehaviorSpec({
- tags(Integration, Insecure, DefaultDatabroker)
+ tags(Integration, Insecure, InsecureDataBroker)
+
+ var databrokerContainer: DataBrokerDockerContainer? = null
+ beforeSpec {
+ databrokerContainer = InsecureDataBrokerDockerContainer()
+ .apply {
+ start()
+ }
+ }
+
+ afterSpec {
+ databrokerContainer?.stop()
+ }
given("A DataBrokerConnectorProvider") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
@@ -53,7 +67,7 @@ class DataBrokerConnectorTest : BehaviorSpec({
dataBrokerConnector.connect()
}
- then("It should throw an exception") {
+ then("It should throw a DataBrokerException") {
exception shouldNotBe null
}
}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerTransporterTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerTransporterTest.kt
index 21b6cdeb..733b868b 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerTransporterTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerTransporterTest.kt
@@ -19,23 +19,40 @@
package org.eclipse.kuksa.connectivity.databroker
import io.grpc.ManagedChannelBuilder
+import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.instanceOf
import io.mockk.mockk
import io.mockk.verify
+import org.eclipse.kuksa.connectivity.databroker.docker.DataBrokerDockerContainer
+import org.eclipse.kuksa.connectivity.databroker.docker.InsecureDataBrokerDockerContainer
import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener
import org.eclipse.kuksa.extensions.updateRandomFloatValue
+import org.eclipse.kuksa.mocking.FriendlyVssPathListener
import org.eclipse.kuksa.proto.v1.KuksaValV1
import org.eclipse.kuksa.proto.v1.Types
-import org.eclipse.kuksa.test.kotest.DefaultDatabroker
import org.eclipse.kuksa.test.kotest.Insecure
+import org.eclipse.kuksa.test.kotest.InsecureDataBroker
import org.eclipse.kuksa.test.kotest.Integration
+import org.eclipse.kuksa.test.kotest.eventuallyConfiguration
import kotlin.random.Random
class DataBrokerTransporterTest : BehaviorSpec({
- tags(Integration, Insecure, DefaultDatabroker)
+ tags(Integration, Insecure, InsecureDataBroker)
+
+ var databrokerContainer: DataBrokerDockerContainer? = null
+ beforeSpec {
+ databrokerContainer = InsecureDataBrokerDockerContainer()
+ .apply {
+ start()
+ }
+ }
+
+ afterSpec {
+ databrokerContainer?.stop()
+ }
given("An active Connection to the DataBroker") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
@@ -121,12 +138,14 @@ class DataBrokerTransporterTest : BehaviorSpec({
Types.Field.FIELD_VALUE,
)
- val vssPathListener = mockk(relaxed = true)
+ val vssPathListener = FriendlyVssPathListener()
subscription.listeners.register(vssPathListener)
then("An Error should be triggered") {
- verify(timeout = 100L) {
- vssPathListener.onError(any())
+ eventually(eventuallyConfiguration) {
+ vssPathListener.errors.count {
+ it.message?.contains("NOT_FOUND") == true
+ } shouldBe 1
}
}
}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/DataBrokerDockerContainer.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/DataBrokerDockerContainer.kt
new file mode 100644
index 00000000..49f05b22
--- /dev/null
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/DataBrokerDockerContainer.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.eclipse.kuksa.connectivity.databroker.docker
+
+import com.github.dockerjava.api.DockerClient
+import com.github.dockerjava.api.command.CreateContainerResponse
+import com.github.dockerjava.api.command.PullImageResultCallback
+import com.github.dockerjava.api.command.WaitContainerResultCallback
+import com.github.dockerjava.api.exception.NotModifiedException
+import com.github.dockerjava.api.model.ExposedPort
+import com.github.dockerjava.api.model.HostConfig
+import com.github.dockerjava.api.model.InternetProtocol
+import com.github.dockerjava.api.model.PortBinding
+import com.github.dockerjava.api.model.Ports
+import com.github.dockerjava.core.DefaultDockerClientConfig
+import com.github.dockerjava.core.DockerClientImpl
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient
+import org.eclipse.kuksa.connectivity.databroker.DATABROKER_TIMEOUT_SECONDS
+import java.time.Duration
+import java.util.concurrent.TimeUnit
+
+private const val KEY_PROPERTY_DATABROKER_TAG = "databroker.tag"
+private const val KEY_ENV_DATABROKER_TAG = "DATABROKER_TAG"
+private const val DEFAULT_DATABROKER_TAG = "master"
+
+/**
+ * Starts and stops the Databroker Docker Container. Per default the image with the master tag is pulled and started.
+ * The version of the image can be influenced by either a System Property with value "databroker.tag" or an
+ * Environment Variable with value "DATABROKER_TAG".
+ *```
+ * Samples
+ *
+ * System Property:
+ * ./gradlew clean build -Ddatabroker.tag="0.4.1"
+ *
+ *
+ * Environment Variable:
+ * export DATABROKER_TAG="0.4.1"
+ * ./gradlew clean build
+ *
+ * or
+ *
+ * DATABROKER_TAG="0.4.1" ./gradlew clean build
+ * ```
+ */
+abstract class DataBrokerDockerContainer(
+ protected val containerName: String,
+ protected val port: Int,
+) {
+ protected open val hostConfig: HostConfig = HostConfig.newHostConfig()
+ .withNetworkMode("host")
+ .withAutoRemove(true)
+ .withPortBindings(
+ PortBinding(
+ Ports.Binding("0.0.0.0", ""),
+ ExposedPort(port, InternetProtocol.TCP),
+ ),
+ )
+
+ protected val repository = "ghcr.io/eclipse/kuksa.val/databroker"
+
+ private val databrokerTag = System.getProperty(KEY_PROPERTY_DATABROKER_TAG)
+ ?: System.getenv(KEY_ENV_DATABROKER_TAG)
+ ?: DEFAULT_DATABROKER_TAG
+
+ protected val dockerClient: DockerClient
+
+ init {
+ val config = DefaultDockerClientConfig.createDefaultConfigBuilder().build()
+
+ val timeout = Duration.ofSeconds(DATABROKER_TIMEOUT_SECONDS)
+ val dockerHttpClient = ApacheDockerHttpClient.Builder()
+ .dockerHost(config.dockerHost)
+ .sslConfig(config.sslConfig)
+ .connectionTimeout(timeout)
+ .responseTimeout(timeout)
+ .build()
+
+ dockerClient = DockerClientImpl.getInstance(config, dockerHttpClient)
+ }
+
+ fun start() {
+ stop()
+
+ pullImage(databrokerTag)
+ val databrokerContainer = createContainer(databrokerTag)
+ startContainer(databrokerContainer.id)
+ }
+
+ fun stop() {
+ val databrokerNames = listOf(containerName)
+ val dockerContainers = dockerClient.listContainersCmd()
+ .withNameFilter(databrokerNames)
+ .exec()
+
+ dockerContainers.forEach { container ->
+ try {
+ dockerClient.stopContainerCmd(container.id).exec()
+ } catch (_: NotModifiedException) {
+ // thrown when a container is already stopped
+ }
+ }
+ }
+
+ private fun pullImage(tag: String) {
+ dockerClient.pullImageCmd(repository)
+ .withTag(tag)
+ .exec(PullImageResultCallback())
+ .awaitCompletion(30, TimeUnit.SECONDS)
+ }
+
+ protected abstract fun createContainer(tag: String): CreateContainerResponse
+
+ private fun startContainer(containerId: String) {
+ try {
+ dockerClient.startContainerCmd(containerId).exec()
+
+ dockerClient.waitContainerCmd(containerId)
+ .exec(WaitContainerResultCallback())
+ .awaitCompletion(5, TimeUnit.SECONDS)
+ } catch (_: NotModifiedException) {
+ // thrown when a container is already started
+ }
+ }
+}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/InsecureDataBrokerDockerContainer.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/InsecureDataBrokerDockerContainer.kt
new file mode 100644
index 00000000..c6e9ab1e
--- /dev/null
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/InsecureDataBrokerDockerContainer.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.eclipse.kuksa.connectivity.databroker.docker
+
+import com.github.dockerjava.api.command.CreateContainerResponse
+import org.eclipse.kuksa.connectivity.databroker.DATABROKER_CONTAINER_NAME
+import org.eclipse.kuksa.connectivity.databroker.DATABROKER_PORT
+
+// no tls, no authentication
+class InsecureDataBrokerDockerContainer(
+ containerName: String = DATABROKER_CONTAINER_NAME,
+ port: Int = DATABROKER_PORT,
+) : DataBrokerDockerContainer(containerName, port) {
+
+ @Suppress("ArgumentListWrapping", "ktlint:standard:argument-list-wrapping") // better key-value pair readability
+ override fun createContainer(tag: String): CreateContainerResponse {
+ return dockerClient.createContainerCmd("$repository:$tag")
+ .withName(containerName)
+ .withHostConfig(hostConfig)
+ .withCmd(
+ "--port", "$port",
+ "--insecure",
+ )
+ .exec()
+ }
+}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/SecureDataBrokerDockerContainer.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/SecureDataBrokerDockerContainer.kt
new file mode 100644
index 00000000..733c0121
--- /dev/null
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/docker/SecureDataBrokerDockerContainer.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.eclipse.kuksa.connectivity.databroker.docker
+
+import com.github.dockerjava.api.command.CreateContainerResponse
+import com.github.dockerjava.api.model.AccessMode
+import com.github.dockerjava.api.model.Bind
+import com.github.dockerjava.api.model.HostConfig
+import com.github.dockerjava.api.model.Volume
+import org.eclipse.kuksa.connectivity.databroker.DATABROKER_CONTAINER_NAME
+import org.eclipse.kuksa.connectivity.databroker.DATABROKER_PORT
+import org.eclipse.kuksa.test.TestResourceFile
+
+// tls enabled, authentication enabled
+class SecureDataBrokerDockerContainer(
+ containerName: String = DATABROKER_CONTAINER_NAME,
+ port: Int = DATABROKER_PORT,
+) : DataBrokerDockerContainer(containerName, port) {
+
+ private val authenticationFolder = TestResourceFile("authentication").toString()
+ private val authenticationMount = "/resources/authentication"
+
+ private val tlsFolder = TestResourceFile("tls").toString()
+ private val tlsMount = "/resources/tls"
+
+ override val hostConfig: HostConfig = super.hostConfig
+ .withBinds(
+ Bind(tlsFolder, Volume(tlsMount), AccessMode.ro),
+ Bind(authenticationFolder, Volume(authenticationMount), AccessMode.ro),
+ )
+
+ @Suppress("ArgumentListWrapping", "ktlint:standard:argument-list-wrapping") // better key-value pair readability
+ override fun createContainer(tag: String): CreateContainerResponse {
+ return dockerClient.createContainerCmd("$repository:$tag")
+ .withName(containerName)
+ .withHostConfig(hostConfig)
+ .withCmd(
+ "--port", "$port",
+ "--tls-cert", "$tlsMount/Server.pem",
+ "--tls-private-key", "$tlsMount/Server.key",
+ "--jwt-public-key", "$authenticationMount/jwt.key.pub",
+ )
+ .exec()
+ }
+}
diff --git a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/subscription/DataBrokerSubscriberTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/subscription/DataBrokerSubscriberTest.kt
index 3c174221..d065c32a 100644
--- a/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/subscription/DataBrokerSubscriberTest.kt
+++ b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/subscription/DataBrokerSubscriberTest.kt
@@ -22,13 +22,15 @@ import io.kotest.assertions.nondeterministic.continually
import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
-import kotlinx.coroutines.delay
import org.eclipse.kuksa.connectivity.databroker.DataBrokerConnectorProvider
import org.eclipse.kuksa.connectivity.databroker.DataBrokerTransporter
+import org.eclipse.kuksa.connectivity.databroker.docker.DataBrokerDockerContainer
+import org.eclipse.kuksa.connectivity.databroker.docker.InsecureDataBrokerDockerContainer
import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener
import org.eclipse.kuksa.extensions.toggleBoolean
import org.eclipse.kuksa.extensions.updateRandomFloatValue
@@ -38,15 +40,27 @@ import org.eclipse.kuksa.mocking.FriendlyVssPathListener
import org.eclipse.kuksa.pattern.listener.MultiListener
import org.eclipse.kuksa.pattern.listener.count
import org.eclipse.kuksa.proto.v1.Types
-import org.eclipse.kuksa.test.kotest.DefaultDatabroker
import org.eclipse.kuksa.test.kotest.Insecure
+import org.eclipse.kuksa.test.kotest.InsecureDataBroker
import org.eclipse.kuksa.test.kotest.Integration
+import org.eclipse.kuksa.test.kotest.continuallyConfiguration
+import org.eclipse.kuksa.test.kotest.eventuallyConfiguration
import org.eclipse.kuksa.vssNode.VssDriver
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
class DataBrokerSubscriberTest : BehaviorSpec({
- tags(Integration, Insecure, DefaultDatabroker)
+ tags(Integration, Insecure, InsecureDataBroker)
+
+ var databrokerContainer: DataBrokerDockerContainer? = null
+ beforeSpec {
+ databrokerContainer = InsecureDataBrokerDockerContainer()
+ .apply {
+ start()
+ }
+ }
+
+ afterSpec {
+ databrokerContainer?.stop()
+ }
given("An active Connection to the DataBroker") {
val dataBrokerConnectorProvider = DataBrokerConnectorProvider()
@@ -64,15 +78,18 @@ class DataBrokerSubscriberTest : BehaviorSpec({
val vssPathListener = FriendlyVssPathListener()
classUnderTest.subscribe(vssPath, fieldValue, vssPathListener)
- then("The VssPathListener should send out ONE update containing ALL children") {
- eventually(1.seconds) {
- vssPathListener.updates.size shouldBe 1
- }
+ then("The VssPathListener should send out an update containing ALL children") {
+ eventually(eventuallyConfiguration) {
+ val foundUpdate = vssPathListener.updates.find { entryUpdates ->
+ entryUpdates.size == 3 // all children: IsEnabled, IsEngaged, IsError
+ }
- val entryUpdates = vssPathListener.updates[0]
- vssPathListener.updates.size shouldBe 1 // ONE update
- entryUpdates.size shouldBe 3 // all children: IsEnabled, IsEngaged, IsError
- entryUpdates.all { it.entry.path.startsWith(vssPath) } shouldBe true
+ foundUpdate shouldNotBe null
+ foundUpdate?.size shouldBe 3
+ foundUpdate?.count { it.entry.path.endsWith(".IsEnabled") } shouldBe 1
+ foundUpdate?.count { it.entry.path.endsWith(".IsEngaged") } shouldBe 1
+ foundUpdate?.count { it.entry.path.endsWith(".IsError") } shouldBe 1
+ }
}
`when`("Any child changes it's value") {
@@ -84,23 +101,21 @@ class DataBrokerSubscriberTest : BehaviorSpec({
val newValueIsEngaged = databrokerTransporter.toggleBoolean(vssPathIsEngaged)
then("The VssPathListener should be notified about it") {
- eventually(1.seconds) {
- vssPathListener.updates.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ val entryUpdates = vssPathListener.updates.flatten()
+ entryUpdates.count {
+ val path = it.entry.path
+ val entry = it.entry
+ val value = entry.value
+ path == vssPathIsError && value.bool == newValueIsError
+ } shouldBe 1
+ entryUpdates.count {
+ val path = it.entry.path
+ val entry = it.entry
+ val value = entry.value
+ path == vssPathIsEngaged && value.bool == newValueIsEngaged
+ } shouldBe 1
}
-
- val entryUpdates = vssPathListener.updates.flatten()
- entryUpdates.count {
- val path = it.entry.path
- val entry = it.entry
- val value = entry.value
- path == vssPathIsError && value.bool == newValueIsError
- } shouldBe 1
- entryUpdates.count {
- val path = it.entry.path
- val entry = it.entry
- val value = entry.value
- path == vssPathIsEngaged && value.bool == newValueIsEngaged
- } shouldBe 1
}
}
}
@@ -115,21 +130,19 @@ class DataBrokerSubscriberTest : BehaviorSpec({
val updateRandomFloatValue = databrokerTransporter.updateRandomFloatValue(vssPath)
then("The VssPathListener is notified about the change") {
- eventually(1.seconds) {
- vssPathListener.updates.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ vssPathListener.updates.flatten()
+ .count {
+ val dataEntry = it.entry
+ val datapoint = dataEntry.value
+ dataEntry.path == vssPath && datapoint.float == updateRandomFloatValue
+ } shouldBe 1
}
- vssPathListener.updates.flatten()
- .count {
- val dataEntry = it.entry
- val datapoint = dataEntry.value
- dataEntry.path == vssPath && datapoint.float == updateRandomFloatValue
- } shouldBe 1
-
- vssPathListener.updates.clear()
}
}
`when`("Subscribing the same VssPathListener to a different vssPath") {
+ vssPathListener.reset()
val otherVssPath = "Vehicle.ADAS.CruiseControl.SpeedSet"
classUnderTest.subscribe(otherVssPath, fieldValue, vssPathListener)
@@ -139,25 +152,22 @@ class DataBrokerSubscriberTest : BehaviorSpec({
val updatedValueOtherVssPath = databrokerTransporter.updateRandomFloatValue(otherVssPath)
then("The Observer is notified about both changes") {
- eventually(1.seconds) {
- vssPathListener.updates.size shouldBe 3 // 1 from subscribe(otherVssPath) + 2 updates
+ eventually(eventuallyConfiguration) {
+ val entryUpdates = vssPathListener.updates.flatten()
+ entryUpdates
+ .count {
+ val path = it.entry.path
+ val value = it.entry.value
+ path == vssPath && value.float == updatedValueVssPath
+ } shouldBe 1
+
+ entryUpdates
+ .count {
+ val path = it.entry.path
+ val value = it.entry.value
+ path == otherVssPath && value.float == updatedValueOtherVssPath
+ } shouldBe 1
}
-
- val entryUpdates = vssPathListener.updates.flatten()
- entryUpdates
- .count {
- val path = it.entry.path
- val entry = it.entry
- val value = entry.value
- path == vssPath && value.float == updatedValueVssPath
- } shouldBe 1
- entryUpdates
- .count {
- val path = it.entry.path
- val entry = it.entry
- val value = entry.value
- path == otherVssPath && value.float == updatedValueOtherVssPath
- } shouldBe 1
}
}
}
@@ -176,13 +186,14 @@ class DataBrokerSubscriberTest : BehaviorSpec({
then("Each VssPathListener is only notified once") {
friendlyVssPathListeners.forEach { listener ->
- eventually(1.seconds) {
- listener.updates.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ listener.updates.flatten()
+ .count {
+ val path = it.entry.path
+ val value = it.entry.value
+ path == vssPath && value.float == randomFloatValue
+ } shouldBe 1
}
-
- val count = listener.updates
- .count { it[0].entry.value.float == randomFloatValue }
- count shouldBe 1
}
}
}
@@ -194,10 +205,9 @@ class DataBrokerSubscriberTest : BehaviorSpec({
and("When the FIELD_VALUE of Vehicle.Speed is updated") {
databrokerTransporter.updateRandomFloatValue(vssPath)
- delay(100)
then("The VssPathListener is not notified") {
- continually(100.milliseconds) {
+ continually(continuallyConfiguration) {
vssPathListener.updates.size shouldBe 0
}
}
@@ -216,13 +226,15 @@ class DataBrokerSubscriberTest : BehaviorSpec({
val randomFloatValue = databrokerTransporter.updateRandomFloatValue(vssPath)
then("The VssPathListener is only notified once") {
- eventually(1.seconds) {
- vssPathListener.updates.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ val count = vssPathListener.updates.flatten()
+ .count {
+ val path = it.entry.path
+ val value = it.entry.value
+ path == vssPath && value.float == randomFloatValue
+ }
+ count shouldBe 1
}
-
- val count = vssPathListener.updates
- .count { it[0].entry.value.float == randomFloatValue }
- count shouldBe 1
}
}
}
@@ -242,13 +254,13 @@ class DataBrokerSubscriberTest : BehaviorSpec({
databrokerTransporter.updateRandomUint32Value(vssHeartRate.vssPath)
then("The Observer should be triggered") {
- eventually(1.seconds) {
- friendlyVssNodeListener.updatedVssNodes.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ friendlyVssNodeListener.updatedVssNodes.count {
+ val vssPath = it.vssPath
+ val value = it.value
+ vssPath == vssHeartRate.vssPath && value == randomIntValue
+ } shouldBe 1
}
-
- val count = friendlyVssNodeListener.updatedVssNodes
- .count { it.value == randomIntValue }
- count shouldBe 1
}
}
}
@@ -271,12 +283,13 @@ class DataBrokerSubscriberTest : BehaviorSpec({
databrokerTransporter.updateRandomUint32Value(vssHeartRate.vssPath)
then("The Observer is only notified once") {
- eventually(1.seconds) {
- nodeListenerMock.updatedVssNodes.size shouldBe 2
+ eventually(eventuallyConfiguration) {
+ nodeListenerMock.updatedVssNodes.count {
+ val vssPath = it.vssPath
+ val value = it.value
+ vssPath == vssHeartRate.vssPath && value == randomIntValue
+ } shouldBe 1
}
-
- val count = nodeListenerMock.updatedVssNodes.count { it.value == randomIntValue }
- count shouldBe 1
}
}
}
diff --git a/kuksa-sdk/src/test/resources/actuate-provide-all.token b/kuksa-sdk/src/test/resources/authentication/actuate-provide-all.token
similarity index 100%
rename from kuksa-sdk/src/test/resources/actuate-provide-all.token
rename to kuksa-sdk/src/test/resources/authentication/actuate-provide-all.token
diff --git a/kuksa-sdk/src/test/resources/authentication/jwt.key.pub b/kuksa-sdk/src/test/resources/authentication/jwt.key.pub
new file mode 100644
index 00000000..d9f78534
--- /dev/null
+++ b/kuksa-sdk/src/test/resources/authentication/jwt.key.pub
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6ScE9EKXEWVyYhzfhfvg
++LC8NseiuEjfrdFx3HKkb31bRw/SeS0Rye0KDP7uzffwreKf6wWYGxVUPYmyKC7j
+Pji5MpDBGM9r3pIZSvPUFdpTE5TiRHFBxWbqPSYt954BTLq4rMu/W+oq5Pdfnugb
+voYpLf0dclBl1g9KyszkDnItz3TYbWhGMbsUSfyeSPzH0IADzLoifxbc5mgiR73N
+CA/4yNSpfLoqWgQ2vdTM1182sMSmxfqSgMzIMUX/tiaXGdkoKITF1sULlLyWfTo9
+79XRZ0hmUwvfzr3OjMZNoClpYSVbKY+vtxHyux9KOOtv9lPMsgYIaPXvisrsneDZ
+fCS0afOfjgR96uHIe2UPSGAXru3yGziqEfpRZoxsgXaOe905ordLD5bSX14xkN7N
+Cz7rxDLlxPQyxp4Vhog7p/QeUyydBpZjq2bAE5GAJtiu+XGvG8RypzJFKFQwMNsw
+g1BoZVD0mb0MtU8KQmHcZIfY0FVer/CR0mUjfl1rHbtoJB+RY03lQvYNAD04ibAG
+NI1RhlTziu35Xo6NDEgs9hVs9k3WrtF+ZUxhivWmP2VXhWruRakVkC1NzKGh54e5
+/KlluFbBNpWgvWZqzWo9Jr7/fzHtR0Q0IZwkxh+Vd/bUZya1uLKqP+sTcc+aTHbn
+AEiqOjPq0D6X45wCzIwjILUCAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/kuksa-sdk/src/test/resources/provide-all.token b/kuksa-sdk/src/test/resources/authentication/provide-all.token
similarity index 100%
rename from kuksa-sdk/src/test/resources/provide-all.token
rename to kuksa-sdk/src/test/resources/authentication/provide-all.token
diff --git a/kuksa-sdk/src/test/resources/read-all.token b/kuksa-sdk/src/test/resources/authentication/read-all.token
similarity index 100%
rename from kuksa-sdk/src/test/resources/read-all.token
rename to kuksa-sdk/src/test/resources/authentication/read-all.token
diff --git a/kuksa-sdk/src/test/resources/CA.pem b/kuksa-sdk/src/test/resources/tls/CA.pem
similarity index 100%
rename from kuksa-sdk/src/test/resources/CA.pem
rename to kuksa-sdk/src/test/resources/tls/CA.pem
diff --git a/kuksa-sdk/src/test/resources/tls/Server.key b/kuksa-sdk/src/test/resources/tls/Server.key
new file mode 100644
index 00000000..2e0e5f9a
--- /dev/null
+++ b/kuksa-sdk/src/test/resources/tls/Server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA0FXTgl4B1nXO0AhHGwPWv/nD73sAnyhSUqUHKUq9WFg/u2ML
+YX9OFjAgYvXTQGkEdGYJc6e/8TtNae8hQYsX6JVT669yg5vatIf3/GVhvxhngNwp
+arLGYXyRV7QCgoBrdzpbTuUhhw51kWzgj1oUw+/XUTrepAvzZa6dh1ieKQSfmJUi
+6OdcFCagIC1gVAXBv8fPQKjoznmtXs4rmryKfstgtU4ZXHx+wDQaafoUiveZbfVn
+toi4UW926+IUI6xD4y3wVVoJyiit//T96eJ7QQsfeSvQGufTSTr3jS+w3oUpG///
+UZCLdFWfitZk5z3ERGY/ysmGXuRrDKOI1lJsaQIDAQABAoIBADbye/+FVcy/c5Vw
+qXhZkdk+Qcw2z9oqY1QCzJm6aagUVnLQ056aWfkGJnSTS21QhWlBxDppwvkX3/oR
+pN7Jlbu1LtYZhwVpJMy6k9BQ2O/yutHEv3OxxRrPJPJRutu6jR18Gbr8OdSKHr4F
+TlDgvFtVNZ/p+/CeakqmPEwHNmewo6IfpkBNKHDmi1NpYoEFoyfb0XDVH8m87k5A
+fso1hlDbzPQlJ840ebaf0DQBmeQNE3r5NeOMaMOPZzHUY2Ps/+AtCRUhIiAaw7cI
+qkW3Vqkg6Lm1zjWv4ZUVsxz6mPKEzYX94CFPKIlWFDOLXYx/Ne1c2O2IIyyaUFrh
+sD3dESUCgYEA6+N90wM0JahyOytwlHwmFtycqY8cNd1HGpR50C6Tly4ogsBD6j6h
+UNnUdbDDqiI4y+ZAle+z4hqboW2UWc3yHPZG7Lxn94PgE7HLCCOsz8Uwpm2Xord0
+Uk4yQANzn1/PmbOubxIsTVhz37gotSEj/NFgU0r61bpeDBBHCd2uU18CgYEA4hj0
+S2F6+xbAbpVgdDBCxubMKbKT9jaxVgjr+M22Vrt+EM1zGkIQUhhpHCS5n6h9hmT3
+JJ2UZDuYD/J0N5kBcCVkHWFQZVfhURmQ6xcYgJmz97XkXNlTrAkKZwSXnud3wVY6
+ooz6yz71eA6E+Jln9SVnjO+kBnVvWylqD4OLXTcCgYEA3E+PX7o5RQarEbpDnlrJ
+VEbdhrujlGdDln6fuqEVtXgl2+BoTeoKzjF0bisWw9rMgxtcrOzAa+d//WgTy0A+
+5W/a1BYvYAvqB1rhjouLRk4cXwQyQIXo/UoNQp42qd5ZTWt0+kXV3LNFHeipFGqM
+av6+YWzBE0bJuaimQH+r5i8CgYEA4QOixQ6LvS4Eb0m8h4WsP5VcZLcz6BrAXHZe
+mevo5uKL8R72yZAr+/gNS5QFJK8j1cfP6qHlF+fzSxOps9dThg/AVjkOMP3H4NWH
+01/V486UPBfK4NjtG86XirUYTG3iUgiGLFYQFoxe8Y/JqDvMKT6DktDANilTnK9X
+vX7WCPsCgYEAjMy6OfWhSeGMFYB8QTUkIIf57mdSGYbpVxEh8UnpZJPq62AZj+FX
+Rzyu5CLLkg3PTVOyzsXnBOxBwkjje72Wv7tKaq9uKYkNPyeYIiM5VZTEKzPOAQTJ
+eO3xy/LbOM1Plfy/koGN5FiHLL+EBdKn42F+ytj0FEYls0V0sZ+kbcQ=
+-----END RSA PRIVATE KEY-----
diff --git a/kuksa-sdk/src/test/resources/tls/Server.pem b/kuksa-sdk/src/test/resources/tls/Server.pem
new file mode 100644
index 00000000..4796388c
--- /dev/null
+++ b/kuksa-sdk/src/test/resources/tls/Server.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEvDCCA6SgAwIBAgIUficQzSD+1RjwvQQBvs3jGdvrkSgwDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMQ8wDQYDVQQHDAZP
+dHRhd2ExJTAjBgNVBAoMHEVjbGlwc2Uub3JnIEZvdW5kYXRpb24sIEluYy4xFTAT
+BgNVBAMMDGxvY2FsaG9zdC1jYTEkMCIGCSqGSIb3DQEJARYVa3Vrc2EtZGV2QGVj
+bGlwc2Uub3JnMB4XDTIzMDYxNjEwMDQxNloXDTI0MDYxNTEwMDQxNlowgY4xCzAJ
+BgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMQ8wDQYDVQQHDAZPdHRhd2ExJTAj
+BgNVBAoMHEVjbGlwc2Uub3JnIEZvdW5kYXRpb24sIEluYy4xDzANBgNVBAMMBlNl
+cnZlcjEkMCIGCSqGSIb3DQEJARYVa3Vrc2EtZGV2QGVjbGlwc2Uub3JnMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0FXTgl4B1nXO0AhHGwPWv/nD73sA
+nyhSUqUHKUq9WFg/u2MLYX9OFjAgYvXTQGkEdGYJc6e/8TtNae8hQYsX6JVT669y
+g5vatIf3/GVhvxhngNwparLGYXyRV7QCgoBrdzpbTuUhhw51kWzgj1oUw+/XUTre
+pAvzZa6dh1ieKQSfmJUi6OdcFCagIC1gVAXBv8fPQKjoznmtXs4rmryKfstgtU4Z
+XHx+wDQaafoUiveZbfVntoi4UW926+IUI6xD4y3wVVoJyiit//T96eJ7QQsfeSvQ
+GufTSTr3jS+w3oUpG///UZCLdFWfitZk5z3ERGY/ysmGXuRrDKOI1lJsaQIDAQAB
+o4IBCDCCAQQwIgYDVR0RBBswGYIGU2VydmVygglsb2NhbGhvc3SHBH8AAAEwHQYD
+VR0OBBYEFGC69Htc/kDNUmsFH8hPB0ZGdkQHMIG+BgNVHSMEgbYwgbOhgZqkgZcw
+gZQxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMQ8wDQYDVQQHDAZPdHRh
+d2ExJTAjBgNVBAoMHEVjbGlwc2Uub3JnIEZvdW5kYXRpb24sIEluYy4xFTATBgNV
+BAMMDGxvY2FsaG9zdC1jYTEkMCIGCSqGSIb3DQEJARYVa3Vrc2EtZGV2QGVjbGlw
+c2Uub3JnghR92Q2Epkh+nIVseTvM7XpxzB0XwjANBgkqhkiG9w0BAQsFAAOCAQEA
+pGgKVbE6AwnSBmCy/Q1tCSqKQj2ILpOZBNBBpAqbu/vUUy9XL4DKKX/Oi5FnRtJk
+VgsBg3jVK5NL00FR7bBb/8WLJHr+lTARk8SFOpReP+8vJap4G6vIJYvmJhvLurvH
+axJauc81YnookPMjocjm9WW570UKNR3Yac72lwGcoOUnhLkkGzMvW0/xBoVhaVGh
+dPCFSuS65ulpB8TYATyEasrtaC9wYlmOxX/FhkgM7MRsHc4XJyEMmPVxJNBgESL4
+Ca/J0lUvnELAP6GndfXaiJ7VaLp7RbRcHfMVfHsy3buF2pVwr2aMteBOl2WBv7NS
+divXduydfcBGtdJ1/a70CA==
+-----END CERTIFICATE-----
diff --git a/kuksa-sdk/src/testDebug/kotlin/org/eclipse/kuksa/connectivity/databroker/DebugDataBrokerConfig.kt b/kuksa-sdk/src/testDebug/kotlin/org/eclipse/kuksa/connectivity/databroker/DebugDataBrokerConfig.kt
new file mode 100644
index 00000000..3790bea6
--- /dev/null
+++ b/kuksa-sdk/src/testDebug/kotlin/org/eclipse/kuksa/connectivity/databroker/DebugDataBrokerConfig.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.eclipse.kuksa.connectivity.databroker
+
+const val DATABROKER_PORT = 55557
+const val DATABROKER_CONTAINER_NAME = "databroker_testDebug"
diff --git a/kuksa-sdk/src/testRelease/kotlin/org/eclipse/kuksa/connectivity/databroker/ReleaseDataBrokerConfig.kt b/kuksa-sdk/src/testRelease/kotlin/org/eclipse/kuksa/connectivity/databroker/ReleaseDataBrokerConfig.kt
new file mode 100644
index 00000000..f711c133
--- /dev/null
+++ b/kuksa-sdk/src/testRelease/kotlin/org/eclipse/kuksa/connectivity/databroker/ReleaseDataBrokerConfig.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2023 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.eclipse.kuksa.connectivity.databroker
+
+const val DATABROKER_PORT = 55558
+const val DATABROKER_CONTAINER_NAME = "databroker_testRelease"
diff --git a/test/src/main/java/org/eclipse/kuksa/test/extension/FloatExtension.kt b/test/src/main/java/org/eclipse/kuksa/test/extension/FloatExtension.kt
new file mode 100644
index 00000000..87919705
--- /dev/null
+++ b/test/src/main/java/org/eclipse/kuksa/test/extension/FloatExtension.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.eclipse.kuksa.test.extension
+
+import kotlin.math.abs
+
+fun Float.equals(other: Float, epsilon: Float): Boolean {
+ return abs(this - other) <= epsilon
+}
diff --git a/test/src/main/java/org/eclipse/kuksa/test/kotest/KotestProjectConfig.kt b/test/src/main/java/org/eclipse/kuksa/test/kotest/KotestProjectConfig.kt
index 3e27f46b..310ffffe 100644
--- a/test/src/main/java/org/eclipse/kuksa/test/kotest/KotestProjectConfig.kt
+++ b/test/src/main/java/org/eclipse/kuksa/test/kotest/KotestProjectConfig.kt
@@ -19,7 +19,18 @@
package org.eclipse.kuksa.test.kotest
+import io.kotest.assertions.nondeterministic.continuallyConfig
+import io.kotest.assertions.nondeterministic.eventuallyConfig
import io.kotest.core.config.AbstractProjectConfig
+import kotlin.time.Duration.Companion.seconds
+
+val eventuallyConfiguration = eventuallyConfig {
+ duration = 1.seconds
+}
+
+val continuallyConfiguration = continuallyConfig {
+ duration = 1.seconds
+}
// https://kotest.io/docs/framework/project-config.html
object KotestProjectConfig : AbstractProjectConfig() {
diff --git a/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt b/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt
index 01ec66ad..e4906db1 100644
--- a/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt
+++ b/test/src/main/java/org/eclipse/kuksa/test/kotest/Tag.kt
@@ -22,10 +22,13 @@ package org.eclipse.kuksa.test.kotest
import io.kotest.core.NamedTag
val Integration = NamedTag("Integration")
-val DefaultDatabroker = NamedTag("DefaultDatabroker") // unsecure => no tls, no authentication
-val CustomDatabroker = NamedTag("CustomDatabroker")
-val Secure = NamedTag("Secure") // tls enabled, no authentication
-val Authentication = NamedTag("Authentication") // no tls, authentication enabled
-val Unit = NamedTag("Unit")
+val InsecureDataBroker = NamedTag("InsecureDataBroker") // no tls, no authentication
val Insecure = NamedTag("Insecure")
+
+val SecureDataBroker = NamedTag("SecureDataBroker")
+val Secure = NamedTag("Secure") // tls enabled, authentication enabled
+val Authentication = NamedTag("Authentication")
+val Tls = NamedTag("Tls")
+
+val Unit = NamedTag("Unit")