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 dd251e7b..0b5b14c8 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 @@ -19,19 +19,27 @@ 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.DockerDatabrokerContainer import org.eclipse.kuksa.connectivity.databroker.docker.DockerSecureDatabrokerContainer 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.Integration 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 @@ -55,17 +63,15 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({ } val random = Random(System.nanoTime()) - val tlsCertificate = TestResourceFile("tls/CA.pem") given("A DataBrokerConnectorProvider") { val dataBrokerConnectorProvider = DataBrokerConnectorProvider() val speedVssPath = "Vehicle.Speed" - and("an insecure DataBrokerConnector with a READ_WRITE_ALL JWT") { + and("a secure DataBrokerConnector with a READ_WRITE_ALL JWT") { val jwtFile = JwtType.READ_WRITE_ALL val dataBrokerConnector = dataBrokerConnectorProvider.createSecure( - rootCertFileStream = tlsCertificate.inputStream(), jwtFileStream = jwtFile.asInputStream(), ) @@ -95,10 +101,9 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({ } } - and("an insecure DataBrokerConnector with a READ_ALL JWT") { + and("a secure DataBrokerConnector with a READ_ALL JWT") { val jwtFile = JwtType.READ_ALL val dataBrokerConnector = dataBrokerConnectorProvider.createSecure( - rootCertFileStream = tlsCertificate.inputStream(), jwtFileStream = jwtFile.asInputStream(), ) @@ -127,10 +132,9 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({ } } - and("an insecure DataBrokerConnector with a READ_WRITE_ALL_VALUES_ONLY JWT") { + and("a secure DataBrokerConnector with a READ_WRITE_ALL_VALUES_ONLY JWT") { val jwtFile = JwtType.READ_WRITE_ALL_VALUES_ONLY val dataBrokerConnector = dataBrokerConnectorProvider.createSecure( - rootCertFileStream = tlsCertificate.inputStream(), jwtFileStream = jwtFile.asInputStream(), ) @@ -182,5 +186,71 @@ class DataBrokerConnectorAuthenticationTest : BehaviorSpec({ } } } + + 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/databroker/DataBrokerConnectionTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectionTest.kt index 7f8a7da4..b5682ad8 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 @@ -23,32 +23,29 @@ import io.grpc.ManagedChannel import io.kotest.assertions.nondeterministic.eventually import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe -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.DockerDatabrokerContainer import org.eclipse.kuksa.connectivity.databroker.docker.DockerInsecureDatabrokerContainer 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.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.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, Insecure, InsecureDatabroker) @@ -66,49 +63,50 @@ class DataBrokerConnectionTest : BehaviorSpec({ } 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 +191,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 +204,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 +221,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 +236,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 +326,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/DataBrokerConnectorTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnectorTest.kt index 79c67d88..6d56a320 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 @@ -67,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 bdceda3c..4544c6c1 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,6 +19,7 @@ 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 @@ -29,11 +30,13 @@ import org.eclipse.kuksa.connectivity.databroker.docker.DockerDatabrokerContaine import org.eclipse.kuksa.connectivity.databroker.docker.DockerInsecureDatabrokerContainer 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.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({ @@ -135,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/subscription/DataBrokerSubscriberTest.kt b/kuksa-sdk/src/test/kotlin/org/eclipse/kuksa/connectivity/databroker/subscription/DataBrokerSubscriberTest.kt index 1b87904a..14b47d78 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,11 +22,11 @@ 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.DockerDatabrokerContainer @@ -43,9 +43,9 @@ import org.eclipse.kuksa.proto.v1.Types 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, InsecureDatabroker) @@ -78,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") { @@ -98,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 } } } @@ -129,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) @@ -153,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 } } } @@ -190,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 } } } @@ -208,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 } } @@ -230,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 } } } @@ -256,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 } } } @@ -285,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/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() {