From 5c4a11ab524b8556e709274d1c233913e8d6a1bd Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jul 2024 08:02:45 +0200 Subject: [PATCH 1/3] test: add kover for test coverage --- Confidence/build.gradle.kts | 1 + Provider/build.gradle.kts | 1 + build.gradle.kts | 1 + 3 files changed, 3 insertions(+) diff --git a/Confidence/build.gradle.kts b/Confidence/build.gradle.kts index b93f80b4..cf4fd84f 100644 --- a/Confidence/build.gradle.kts +++ b/Confidence/build.gradle.kts @@ -6,6 +6,7 @@ plugins { id("signing") kotlin("plugin.serialization").version("1.8.10").apply(true) id("org.jetbrains.kotlinx.binary-compatibility-validator") + id("org.jetbrains.kotlinx.kover") } val providerVersion = project.extra["version"].toString() diff --git a/Provider/build.gradle.kts b/Provider/build.gradle.kts index 5dcb63da..ead81381 100644 --- a/Provider/build.gradle.kts +++ b/Provider/build.gradle.kts @@ -7,6 +7,7 @@ plugins { kotlin("plugin.serialization") id("signing") id("org.jetbrains.kotlinx.binary-compatibility-validator") + id("org.jetbrains.kotlinx.kover") } object Versions { diff --git a/build.gradle.kts b/build.gradle.kts index 059ab795..729d3735 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("com.android.application") version "7.4.2" apply false id("io.github.gradle-nexus.publish-plugin").version("1.3.0").apply(true) id("org.jetbrains.kotlinx.binary-compatibility-validator").version("0.15.0-Beta.3").apply(false) + id("org.jetbrains.kotlinx.kover").version("0.8.2").apply(true) } allprojects { From 1084a452fc0ae28d0929d9b6394c5ae8cf86cb0c Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jul 2024 08:23:16 +0200 Subject: [PATCH 2/3] test: enforce coverage percentage --- Confidence/build.gradle.kts | 10 ++++++++++ Provider/build.gradle.kts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/Confidence/build.gradle.kts b/Confidence/build.gradle.kts index cf4fd84f..22ca84a1 100644 --- a/Confidence/build.gradle.kts +++ b/Confidence/build.gradle.kts @@ -121,4 +121,14 @@ publishing { signing { sign(publishing.publications["release"]) +} + +kover { + reports { + verify { + rule { + minBound(78) + } + } + } } \ No newline at end of file diff --git a/Provider/build.gradle.kts b/Provider/build.gradle.kts index ead81381..0e6eebf2 100644 --- a/Provider/build.gradle.kts +++ b/Provider/build.gradle.kts @@ -123,4 +123,14 @@ publishing { signing { sign(publishing.publications["release"]) +} + +kover { + reports { + verify { + rule { + minBound(70) + } + } + } } \ No newline at end of file From 68d166d22ac9abaeb2aa1c94c1dbd87624edda5b Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jul 2024 09:33:44 +0200 Subject: [PATCH 3/3] test: increase test coverage --- Provider/build.gradle.kts | 2 +- .../ConfidenceValueMappingsTest.kt | 114 ++++++++++++++++++ ...ionTests.kt => ProviderIntegrationTest.kt} | 66 +++++++++- .../confidence/openfeature/TestExtensions.kt | 2 +- .../openfeature/ValueMappingsTest.kt | 82 +++++++++++++ 5 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceValueMappingsTest.kt rename Provider/src/test/java/com/spotify/confidence/openfeature/{ConfidenceIntegrationTests.kt => ProviderIntegrationTest.kt} (63%) create mode 100644 Provider/src/test/java/com/spotify/confidence/openfeature/ValueMappingsTest.kt diff --git a/Provider/build.gradle.kts b/Provider/build.gradle.kts index 0e6eebf2..82c6dbe8 100644 --- a/Provider/build.gradle.kts +++ b/Provider/build.gradle.kts @@ -129,7 +129,7 @@ kover { reports { verify { rule { - minBound(70) + minBound(80) } } } diff --git a/Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceValueMappingsTest.kt b/Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceValueMappingsTest.kt new file mode 100644 index 00000000..aeb5f7df --- /dev/null +++ b/Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceValueMappingsTest.kt @@ -0,0 +1,114 @@ +package com.spotify.confidence.openfeature + +import com.spotify.confidence.ConfidenceValue +import dev.openfeature.sdk.Value +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class ConfidenceValueMappingsTest { + + @Test + fun confidenceStringToValueString() { + val confidenceValue = ConfidenceValue.String("test") + val value = confidenceValue.toValue() + assertEquals("test", value.asString()) + assertNull(value.asInteger()) + assertNull(value.asDate()) + assertNull(value.asBoolean()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceDoubleToValueDouble() { + val confidenceValue = ConfidenceValue.Double(1.23) + val value = confidenceValue.toValue() + assertEquals(1.23, value.asDouble()!!, 0.001) + assertNull(value.asString()) + assertNull(value.asInteger()) + assertNull(value.asDate()) + assertNull(value.asBoolean()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceBooleanToValueBoolean() { + val confidenceValue = ConfidenceValue.Boolean(true) + val value = confidenceValue.toValue() + assertEquals(true, value.asBoolean()) + assertNull(value.asString()) + assertNull(value.asDouble()) + assertNull(value.asDate()) + assertNull(value.asInteger()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceIntegerToValueInteger() { + val confidenceValue = ConfidenceValue.Integer(42) + val value = confidenceValue.toValue() + assertEquals(42, value.asInteger()) + assertNull(value.asString()) + assertNull(value.asDouble()) + assertNull(value.asDate()) + assertNull(value.asBoolean()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceStructToValueStructure() { + val confidenceValue = ConfidenceValue.Struct(mapOf("key" to ConfidenceValue.String("value"))) + val value = confidenceValue.toValue() + assertEquals(mapOf("key" to dev.openfeature.sdk.Value.String("value")), value.asStructure()) + assertNull(value.asString()) + assertNull(value.asDouble()) + assertNull(value.asDate()) + assertNull(value.asBoolean()) + assertNull(value.asList()) + } + + @Test + fun confidenceListToValueList() { + val confidenceValue = ConfidenceValue.List(listOf(ConfidenceValue.String("item"))) + val value = confidenceValue.toValue() + assertEquals(listOf(dev.openfeature.sdk.Value.String("item")), value.asList()) + assertNull(value.asString()) + assertNull(value.asDouble()) + assertNull(value.asDate()) + assertNull(value.asBoolean()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceDateToValueDate() { + val date = java.util.Date() + val confidenceValue = ConfidenceValue.Date(date) + val value = confidenceValue.toValue() + assertEquals(date, value.asDate()) + assertNull(value.asString()) + assertNull(value.asDouble()) + assertNull(value.asBoolean()) + assertNull(value.asInteger()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceTimestampToValueTimestamp() { + val timestamp = java.util.Date() + val confidenceValue = ConfidenceValue.Timestamp(timestamp) + val value = confidenceValue.toValue() + assertEquals(timestamp.time, value.asDate()!!.time) + assertNull(value.asString()) + assertNull(value.asDouble()) + assertNull(value.asBoolean()) + assertNull(value.asInteger()) + assertNull(value.asStructure()) + } + + @Test + fun confidenceNullToValueNull() { + val confidenceValue = ConfidenceValue.Null + val value = confidenceValue.toValue() + assertEquals(Value.Null, value) + } +} \ No newline at end of file diff --git a/Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceIntegrationTests.kt b/Provider/src/test/java/com/spotify/confidence/openfeature/ProviderIntegrationTest.kt similarity index 63% rename from Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceIntegrationTests.kt rename to Provider/src/test/java/com/spotify/confidence/openfeature/ProviderIntegrationTest.kt index 25b9843d..c18a8353 100644 --- a/Provider/src/test/java/com/spotify/confidence/openfeature/ConfidenceIntegrationTests.kt +++ b/Provider/src/test/java/com/spotify/confidence/openfeature/ProviderIntegrationTest.kt @@ -8,6 +8,7 @@ import dev.openfeature.sdk.Reason import dev.openfeature.sdk.Value import dev.openfeature.sdk.events.EventHandler import dev.openfeature.sdk.events.OpenFeatureEvents +import dev.openfeature.sdk.exceptions.ErrorCode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -28,7 +29,7 @@ import java.util.UUID private const val clientSecret = "21wxcxXpU6tKBRFtEFTXYiH7nDqL86Mm" private val mockContext: Context = mock() -class ConfidenceIntegrationTests { +class ProviderIntegrationTest { @get:Rule var tmpFile = TemporaryFolder() @@ -85,7 +86,7 @@ class ConfidenceIntegrationTests { val eventsHandler = EventHandler(Dispatchers.IO).apply { publish(OpenFeatureEvents.ProviderStale) } - val cacheFile = File(mockContext.filesDir, FLAGS_FILE_NAME) + val cacheFile = File(mockContext.filesDir, flagsFileName) assertEquals(0L, cacheFile.length()) val mockConfidence = ConfidenceFactory.create(mockContext, clientSecret) OpenFeatureAPI.setProvider( @@ -118,5 +119,62 @@ class ConfidenceIntegrationTests { assertEquals(Reason.TARGETING_MATCH.name, intDetails.reason) assertNotNull(intDetails.variant) } -} -internal const val FLAGS_FILE_NAME = "confidence_flags_cache.json" \ No newline at end of file + + @Test + fun testSimpleResolveWithFetchAndActivateInMemoryCache() { + val eventsHandler = EventHandler(Dispatchers.IO).apply { + publish(OpenFeatureEvents.ProviderStale) + } + val mockConfidence = ConfidenceFactory.create(mockContext, clientSecret) + + OpenFeatureAPI.setProvider( + ConfidenceFeatureProvider.create( + confidence = mockConfidence, + initialisationStrategy = InitialisationStrategy.ActivateAndFetchAsync, + eventHandler = eventsHandler + ), + ImmutableContext( + targetingKey = UUID.randomUUID().toString(), + attributes = mutableMapOf( + "user" to Value.Structure( + mapOf( + "country" to Value.String("SE") + ) + ) + ) + ) + ) + runBlocking { + awaitProviderReady(eventsHandler = eventsHandler) + } + + val flagNotFoundDetails = OpenFeatureAPI.getClient() + .getObjectDetails( + "kotlin-test-flag", + Value.Structure(emptyMap()) + ) + assertEquals(ErrorCode.FLAG_NOT_FOUND, flagNotFoundDetails.errorCode) + assertNull(flagNotFoundDetails.errorMessage) + assertEquals(Value.Structure(emptyMap()), flagNotFoundDetails.value) + assertEquals(Reason.ERROR.name, flagNotFoundDetails.reason) + assertNull(flagNotFoundDetails.variant) + + runBlocking { + mockConfidence.fetchAndActivate() + } + + val evaluationDetails = OpenFeatureAPI.getClient() + .getObjectDetails( + "kotlin-test-flag", + Value.Structure(emptyMap()) + ) + assertNull(evaluationDetails.errorCode) + assertNull(evaluationDetails.errorMessage) + assertNotNull(evaluationDetails.value) + assertEquals(Reason.TARGETING_MATCH.name, evaluationDetails.reason) + assertNotNull(evaluationDetails.variant) + + assertEquals(4, evaluationDetails.value.asStructure()?.getOrDefault("my-integer", Value.Integer(-1))?.asInteger()) + } + private val flagsFileName = "confidence_flags_cache.json" +} \ No newline at end of file diff --git a/Provider/src/test/java/com/spotify/confidence/openfeature/TestExtensions.kt b/Provider/src/test/java/com/spotify/confidence/openfeature/TestExtensions.kt index 02fa2a91..95f7f0fe 100644 --- a/Provider/src/test/java/com/spotify/confidence/openfeature/TestExtensions.kt +++ b/Provider/src/test/java/com/spotify/confidence/openfeature/TestExtensions.kt @@ -17,7 +17,7 @@ suspend fun awaitProviderReady( dispatcher: CoroutineDispatcher = Dispatchers.IO ) = suspendCancellableCoroutine { continuation -> fun observeProviderReady() = eventsHandler - .observe() + .observe() .onStart { if (eventsHandler.getProviderStatus() == OpenFeatureEvents.ProviderReady) { this.emit(OpenFeatureEvents.ProviderReady) diff --git a/Provider/src/test/java/com/spotify/confidence/openfeature/ValueMappingsTest.kt b/Provider/src/test/java/com/spotify/confidence/openfeature/ValueMappingsTest.kt new file mode 100644 index 00000000..017c1678 --- /dev/null +++ b/Provider/src/test/java/com/spotify/confidence/openfeature/ValueMappingsTest.kt @@ -0,0 +1,82 @@ +package com.spotify.confidence.openfeature + +import com.spotify.confidence.ConfidenceValue +import dev.openfeature.sdk.Value +import org.junit.Assert.assertEquals +import org.junit.Test + +class ValueMappingsTest { + + @Test + fun openFeatureStringValueToConfidenceValueString() { + val value = Value.String("test") + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals("test", confidenceValue.asString()?.string) + } + + @Test + fun openFeatureDoubleValueToConfidenceValueDouble() { + val value = Value.Double(1.23) + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals(1.23, confidenceValue.asDouble()?.double!!, 0.001) + } + + @Test + fun openFeatureBooleanValueToConfidenceValueBoolean() { + val value = Value.Boolean(true) + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals(true, confidenceValue.asBoolean()?.boolean) + } + + @Test + fun openFeatureIntegerValueToConfidenceValueInteger() { + val value = Value.Integer(42) + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals(42, confidenceValue.asInteger()?.integer) + } + + @Test + fun openFeatureStructureValueToConfidenceValueStruct() { + val value = Value.Structure(mapOf("key" to Value.String("value"))) + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals("value", confidenceValue.asStructure()?.map?.get("key")?.asString()?.string) + } + + @Test + fun openFeatureListValueToConfidenceValueList() { + val value = Value.List(listOf(Value.String("value"))) + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals("value", confidenceValue.asList()?.list?.get(0)?.asString()?.string) + } + + @Test + fun mixedListShouldReturnEmptyConfidenceList() { + val value = Value.List(listOf(Value.String("value"), Value.Integer(42))) + val confidenceValue: ConfidenceValue = value.toConfidenceValue() + assertEquals(0, confidenceValue.asList()?.list?.size) + } + + private fun ConfidenceValue.asString(): ConfidenceValue.String? { + return this as? ConfidenceValue.String + } + + private fun ConfidenceValue.asDouble(): ConfidenceValue.Double? { + return this as? ConfidenceValue.Double + } + + private fun ConfidenceValue.asBoolean(): ConfidenceValue.Boolean? { + return this as? ConfidenceValue.Boolean + } + + private fun ConfidenceValue.asInteger(): ConfidenceValue.Integer? { + return this as? ConfidenceValue.Integer + } + + private fun ConfidenceValue.asStructure(): ConfidenceValue.Struct? { + return this as? ConfidenceValue.Struct + } + + private fun ConfidenceValue.asList(): ConfidenceValue.List? { + return this as? ConfidenceValue.List + } +} \ No newline at end of file