Updated on 2026-08-14
This commit is contained in:
parent
5da87df354
commit
e725649059
18 changed files with 661 additions and 421 deletions
|
|
@ -9,6 +9,7 @@ import org.junit.rules.TestRule
|
|||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.Field
|
||||
|
||||
class ApplicationInjectionExecutionRule(
|
||||
private val toggleStates: Map<String, Boolean>,
|
||||
|
|
@ -17,7 +18,7 @@ class ApplicationInjectionExecutionRule(
|
|||
private val tangemApplication: TangemApplication
|
||||
get() = ApplicationProvider.getApplicationContext()
|
||||
|
||||
private var originalFeatureTogglesValues: Map<String, String>? = null
|
||||
private var originalVersionValues: Map<FeatureToggles, String>? = null
|
||||
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
return object : Statement() {
|
||||
|
|
@ -41,10 +42,9 @@ class ApplicationInjectionExecutionRule(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun saveOriginalFeatureToggles() {
|
||||
try {
|
||||
originalFeatureTogglesValues = FeatureToggles.values as Map<String, String>
|
||||
originalVersionValues = FeatureToggles.entries.associateWith { it.version }
|
||||
} catch (e: Exception) {
|
||||
Timber.e("Failed to save original toggles values: ${e.message}")
|
||||
}
|
||||
|
|
@ -52,22 +52,14 @@ class ApplicationInjectionExecutionRule(
|
|||
|
||||
private fun overrideFeatureToggles() {
|
||||
try {
|
||||
val originalValues = originalFeatureTogglesValues ?: FeatureToggles.values
|
||||
val newValues = originalValues.toMutableMap()
|
||||
val versionField = getVersionField()
|
||||
|
||||
toggleStates.forEach { (toggle, enabled) ->
|
||||
if (enabled) {
|
||||
newValues[toggle] = "1.0.0"
|
||||
} else {
|
||||
newValues.remove(toggle)
|
||||
}
|
||||
FeatureToggles.entries.forEach { toggle ->
|
||||
val enabled = toggleStates[toggle.rawName] ?: return@forEach
|
||||
val newVersion = if (enabled) ENABLED_VERSION else DISABLED_VERSION
|
||||
versionField.set(toggle, newVersion)
|
||||
}
|
||||
|
||||
val companionClass = FeatureToggles.Companion::class.java
|
||||
val valuesField = companionClass.getDeclaredField("values")
|
||||
valuesField.isAccessible = true
|
||||
valuesField.set(FeatureToggles.Companion, newValues)
|
||||
|
||||
Timber.i("FeatureToggles.values updated: $toggleStates")
|
||||
} catch (e: Exception) {
|
||||
Timber.e("FeatureToggles.values didn't change with error: ${e.message}")
|
||||
|
|
@ -76,15 +68,27 @@ class ApplicationInjectionExecutionRule(
|
|||
|
||||
private fun restoreOriginalFeatureToggles() {
|
||||
try {
|
||||
if (originalFeatureTogglesValues != null) {
|
||||
val companionClass = FeatureToggles.Companion::class.java
|
||||
val valuesField = companionClass.getDeclaredField("values")
|
||||
valuesField.isAccessible = true
|
||||
valuesField.set(FeatureToggles.Companion, originalFeatureTogglesValues)
|
||||
Timber.i("FeatureToggles.values restored")
|
||||
val saved = originalVersionValues ?: return
|
||||
val versionField = getVersionField()
|
||||
|
||||
saved.forEach { (toggle, originalVersion) ->
|
||||
versionField.set(toggle, originalVersion)
|
||||
}
|
||||
|
||||
Timber.i("FeatureToggles.values restored")
|
||||
} catch (e: Exception) {
|
||||
Timber.e("FeatureToggles.values didn't restored with error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getVersionField(): Field {
|
||||
val field = FeatureToggles::class.java.getDeclaredField("version")
|
||||
field.isAccessible = true
|
||||
return field
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ENABLED_VERSION = "1.0.0"
|
||||
const val DISABLED_VERSION = "undefined"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>Indentation:ExcludedBlockchainToggles.kt$ExcludedBlockchainToggles$ </ID>
|
||||
<ID>Indentation:FeatureToggles.kt$FeatureToggles$ </ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.core.configtoggle.blockchain.impl
|
||||
|
||||
import com.tangem.core.configtoggle.ExcludedBlockchainToggles
|
||||
import com.tangem.core.configtoggle.blockchain.MutableExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.provider.ExcludedBlockchainTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.defineTogglesAvailability
|
||||
import com.tangem.core.configtoggle.utils.toTableString
|
||||
|
|
@ -12,11 +12,13 @@ import kotlin.properties.Delegates
|
|||
/**
|
||||
* [MutableExcludedBlockchainsManager] implementation in dev or mocked build
|
||||
*
|
||||
* @property versionProvider application version provider
|
||||
* @property localTogglesStorage local storage for blockchain toggles
|
||||
* @property versionProvider application version provider
|
||||
* @property excludedBlockchainTogglesProvider provider for excluded blockchain toggle entries
|
||||
* @property localTogglesStorage local storage for blockchain toggles
|
||||
*/
|
||||
internal class DevExcludedBlockchainsManager(
|
||||
private val versionProvider: VersionProvider,
|
||||
private val excludedBlockchainTogglesProvider: ExcludedBlockchainTogglesProvider,
|
||||
private val localTogglesStorage: LocalTogglesStorage,
|
||||
) : MutableExcludedBlockchainsManager {
|
||||
|
||||
|
|
@ -63,6 +65,7 @@ internal class DevExcludedBlockchainsManager(
|
|||
private fun getFileBlockchainToggles(): Map<String, Boolean> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return ExcludedBlockchainToggles.values.defineTogglesAvailability(appVersion = appVersion)
|
||||
return excludedBlockchainTogglesProvider.getToggles()
|
||||
.defineTogglesAvailability(appVersion = appVersion)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
package com.tangem.core.configtoggle.blockchain.impl
|
||||
|
||||
import com.tangem.core.configtoggle.ExcludedBlockchainToggles
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.provider.ExcludedBlockchainTogglesProvider
|
||||
import com.tangem.core.configtoggle.utils.defineTogglesAvailability
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
|
||||
/**
|
||||
* [ExcludedBlockchainsManager] implementation in PROD build
|
||||
*
|
||||
* @property versionProvider application version provider
|
||||
* @property versionProvider application version provider
|
||||
* @property excludedBlockchainTogglesProvider provider for excluded blockchain toggle entries
|
||||
*/
|
||||
internal class ProdExcludedBlockchainsManager(
|
||||
private val versionProvider: VersionProvider,
|
||||
private val excludedBlockchainTogglesProvider: ExcludedBlockchainTogglesProvider,
|
||||
) : ExcludedBlockchainsManager {
|
||||
|
||||
override val excludedBlockchainsIds: Set<String> = getBlockchainToggles()
|
||||
|
|
@ -19,7 +21,7 @@ internal class ProdExcludedBlockchainsManager(
|
|||
private fun getBlockchainToggles(): Set<String> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return ExcludedBlockchainToggles.values
|
||||
return excludedBlockchainTogglesProvider.getToggles()
|
||||
.defineTogglesAvailability(appVersion = appVersion)
|
||||
.filterValues { !it }
|
||||
.keys
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.core.configtoggle.blockchain.provider
|
||||
|
||||
import com.tangem.core.configtoggle.ExcludedBlockchainToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Default implementation of [ExcludedBlockchainTogglesProvider] that reads from the generated
|
||||
* [ExcludedBlockchainToggles] enum.
|
||||
*/
|
||||
internal class DefaultExcludedBlockchainTogglesProvider @Inject constructor() : ExcludedBlockchainTogglesProvider {
|
||||
|
||||
override fun getToggles(): Map<String, String> {
|
||||
return ExcludedBlockchainToggles.entries.associate { it.rawName to it.version }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.configtoggle.blockchain.provider
|
||||
|
||||
/**
|
||||
* Provider for excluded blockchain toggle entries.
|
||||
* This interface allows for easier testing by enabling mock implementations.
|
||||
*/
|
||||
internal interface ExcludedBlockchainTogglesProvider {
|
||||
|
||||
/**
|
||||
* Returns a map of blockchain IDs to their version strings.
|
||||
* The version string is "undefined" for disabled toggles, or a version number like "5.21.0" for enabled ones.
|
||||
*/
|
||||
fun getToggles(): Map<String, String>
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.configtoggle.BuildConfig
|
|||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.impl.DevExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.impl.ProdExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.provider.DefaultExcludedBlockchainTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.version.DefaultVersionProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
|
|
@ -26,17 +27,22 @@ internal object ExcludedBlockchainsManagerModule {
|
|||
appPreferencesStore: AppPreferencesStore,
|
||||
): ExcludedBlockchainsManager {
|
||||
val versionProvider = DefaultVersionProvider(context)
|
||||
val excludedBlockchainTogglesProvider = DefaultExcludedBlockchainTogglesProvider()
|
||||
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DevExcludedBlockchainsManager(
|
||||
versionProvider = versionProvider,
|
||||
excludedBlockchainTogglesProvider = excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage = LocalTogglesStorage(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
preferencesKey = LocalTogglesStorage.EXCLUDED_BLOCKCHAINS_KEY,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
ProdExcludedBlockchainsManager(versionProvider = versionProvider)
|
||||
ProdExcludedBlockchainsManager(
|
||||
versionProvider = versionProvider,
|
||||
excludedBlockchainTogglesProvider = excludedBlockchainTogglesProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.configtoggle.BuildConfig
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.ProdFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.provider.DefaultFeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.version.DefaultVersionProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
|
|
@ -26,17 +27,22 @@ internal object FeatureTogglesManagerModule {
|
|||
appPreferencesStore: AppPreferencesStore,
|
||||
): FeatureTogglesManager {
|
||||
val versionProvider = DefaultVersionProvider(context)
|
||||
val featureTogglesProvider = DefaultFeatureTogglesProvider()
|
||||
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DevFeatureTogglesManager(
|
||||
versionProvider = versionProvider,
|
||||
featureTogglesProvider = featureTogglesProvider,
|
||||
featureTogglesLocalStorage = LocalTogglesStorage(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
preferencesKey = LocalTogglesStorage.FEATURE_TOGGLES_KEY,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
ProdFeatureTogglesManager(versionProvider = versionProvider)
|
||||
ProdFeatureTogglesManager(
|
||||
versionProvider = versionProvider,
|
||||
featureTogglesProvider = featureTogglesProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.core.configtoggle.feature.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.provider.FeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.defineTogglesAvailability
|
||||
import com.tangem.core.configtoggle.utils.toTableString
|
||||
|
|
@ -14,10 +14,12 @@ import kotlin.properties.Delegates
|
|||
* Feature toggles manager implementation in dev or mocked build
|
||||
*
|
||||
* @property versionProvider application version provider
|
||||
* @property featureTogglesProvider provider for feature toggle entries
|
||||
* @property featureTogglesLocalStorage local storage for feature toggles
|
||||
*/
|
||||
internal class DevFeatureTogglesManager(
|
||||
private val versionProvider: VersionProvider,
|
||||
private val featureTogglesProvider: FeatureTogglesProvider,
|
||||
private val featureTogglesLocalStorage: LocalTogglesStorage,
|
||||
) : MutableFeatureTogglesManager {
|
||||
|
||||
|
|
@ -60,7 +62,8 @@ internal class DevFeatureTogglesManager(
|
|||
private fun getFileFeatureToggles(): Map<String, Boolean> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return FeatureToggles.values.defineTogglesAvailability(appVersion = appVersion)
|
||||
return featureTogglesProvider.getToggles()
|
||||
.defineTogglesAvailability(appVersion = appVersion)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
package com.tangem.core.configtoggle.feature.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.provider.FeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.utils.defineTogglesAvailability
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
|
||||
/**
|
||||
* Feature toggles manager implementation in PROD build
|
||||
*
|
||||
* @property versionProvider application version provider
|
||||
* @property versionProvider application version provider
|
||||
* @property featureTogglesProvider provider for feature toggle entries
|
||||
*/
|
||||
internal class ProdFeatureTogglesManager(
|
||||
private val versionProvider: VersionProvider,
|
||||
private val featureTogglesProvider: FeatureTogglesProvider,
|
||||
) : FeatureTogglesManager {
|
||||
|
||||
private val featureToggles: Map<String, Boolean> = getFileFeatureToggles()
|
||||
|
|
@ -22,7 +24,8 @@ internal class ProdFeatureTogglesManager(
|
|||
private fun getFileFeatureToggles(): Map<String, Boolean> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return FeatureToggles.values.defineTogglesAvailability(appVersion = appVersion)
|
||||
return featureTogglesProvider.getToggles()
|
||||
.defineTogglesAvailability(appVersion = appVersion)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.configtoggle.feature.provider
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Default implementation of [FeatureTogglesProvider] that reads from the generated [FeatureToggles] enum.
|
||||
*/
|
||||
internal class DefaultFeatureTogglesProvider @Inject constructor() : FeatureTogglesProvider {
|
||||
|
||||
override fun getToggles(): Map<String, String> {
|
||||
return FeatureToggles.entries.associate { it.rawName to it.version }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.configtoggle.feature.provider
|
||||
|
||||
/**
|
||||
* Provider for feature toggle entries.
|
||||
* This interface allows for easier testing by enabling mock implementations.
|
||||
*/
|
||||
internal interface FeatureTogglesProvider {
|
||||
|
||||
/**
|
||||
* Returns a map of toggle names to their version strings.
|
||||
* The version string is "undefined" for disabled toggles, or a version number like "1.0.0" for enabled ones.
|
||||
*/
|
||||
fun getToggles(): Map<String, String>
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.core.configtoggle.blockchain.impl
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.ExcludedBlockchainToggles
|
||||
import com.tangem.core.configtoggle.blockchain.provider.ExcludedBlockchainTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import io.mockk.*
|
||||
|
|
@ -12,42 +12,74 @@ import org.junit.jupiter.api.*
|
|||
internal class DevExcludedBlockchainsManagerTest {
|
||||
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val excludedBlockchainTogglesProvider = mockk<ExcludedBlockchainTogglesProvider>()
|
||||
private val localTogglesStorage = mockk<LocalTogglesStorage>(relaxUnitFun = true)
|
||||
|
||||
private val testVersion = "5.30"
|
||||
|
||||
private val testToggles = mapOf(
|
||||
"ENABLED_BLOCKCHAIN" to "5.21.0",
|
||||
"DISABLED_BLOCKCHAIN" to "undefined",
|
||||
"FUTURE_BLOCKCHAIN" to "6.0.0",
|
||||
)
|
||||
|
||||
private fun getExpectedFileToggles(appVersion: String?): Map<String, Boolean> {
|
||||
if (appVersion.isNullOrEmpty()) {
|
||||
return testToggles.mapValues { false }
|
||||
}
|
||||
return testToggles.mapValues { (_, version) ->
|
||||
version != "undefined" && isVersionSufficient(appVersion, version)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExcludedBlockchains(appVersion: String?): Set<String> {
|
||||
return getExpectedFileToggles(appVersion).filterValues { !it }.keys
|
||||
}
|
||||
|
||||
private fun isVersionSufficient(appVersion: String, requiredVersion: String): Boolean {
|
||||
val appParts = appVersion.split(".").mapNotNull { it.toIntOrNull() }
|
||||
val reqParts = requiredVersion.split(".").mapNotNull { it.toIntOrNull() }
|
||||
for (i in 0 until maxOf(appParts.size, reqParts.size)) {
|
||||
val app = appParts.getOrElse(i) { 0 }
|
||||
val req = reqParts.getOrElse(i) { 0 }
|
||||
if (app > req) return true
|
||||
if (app < req) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Initialization {
|
||||
|
||||
@BeforeAll
|
||||
fun setupAll() {
|
||||
val toggles = mapOf("CHAIN_1" to "1.0.0", "CHAIN_2" to "2.0.0")
|
||||
mockkObject(ExcludedBlockchainToggles)
|
||||
every { ExcludedBlockchainToggles.values } returns toggles
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDownAll() {
|
||||
unmockkObject(ExcludedBlockchainToggles)
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { excludedBlockchainTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, localTogglesStorage)
|
||||
clearMocks(versionProvider, excludedBlockchainTogglesProvider, localTogglesStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successfully initialize manager`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = "1.0.0"
|
||||
val savedToggles = mapOf("CHAIN_1" to false, "CHAIN_2" to true)
|
||||
val appVersion = testVersion
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val savedToggles = fileToggles.mapValues { !it.value }
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns savedToggles
|
||||
|
||||
// Act
|
||||
val actual = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage).excludedBlockchainsIds
|
||||
val actual = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
).excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
val expected = setOf("CHAIN_1")
|
||||
val expected = savedToggles.filterValues { !it }.keys
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -59,14 +91,20 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@Test
|
||||
fun `successfully initialize manager if versionProvider returns null`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns null
|
||||
val appVersion: String? = null
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
// Act
|
||||
val actual = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage).excludedBlockchainsIds
|
||||
val actual = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
).excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).containsExactly("CHAIN_1", "CHAIN_2")
|
||||
val expected = getExcludedBlockchains(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -77,14 +115,20 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@Test
|
||||
fun `successfully initialize manager if storage returns empty map`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
val appVersion = testVersion
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
// Act
|
||||
val actual = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage).excludedBlockchainsIds
|
||||
val actual = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
).excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).containsExactly("CHAIN_2")
|
||||
val expected = getExcludedBlockchains(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -95,13 +139,19 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@Test
|
||||
fun `failure initialize manager if storage throws exception`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
val appVersion = testVersion
|
||||
val exception = Exception("Test exception")
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } throws exception
|
||||
|
||||
// Act
|
||||
val actual = runCatching { DevExcludedBlockchainsManager(versionProvider, localTogglesStorage) }
|
||||
.exceptionOrNull()!!
|
||||
val actual = runCatching {
|
||||
DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
)
|
||||
}.exceptionOrNull()!!
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isInstanceOf(exception::class.java)
|
||||
|
|
@ -118,21 +168,34 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ExcludeBlockchain {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { excludedBlockchainTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, excludedBlockchainTogglesProvider, localTogglesStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun excludeBlockchain_changesStatusAndSaves() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
every { versionProvider.get() } returns testVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage)
|
||||
manager.excludeBlockchain("CHAIN_1", false)
|
||||
val manager = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
)
|
||||
|
||||
// Act
|
||||
manager.excludeBlockchain("ENABLED_BLOCKCHAIN", false)
|
||||
val actual = manager.excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).contains("CHAIN_1")
|
||||
coVerify { localTogglesStorage.store(match { it["CHAIN_1"] == false }) }
|
||||
Truth.assertThat(actual).contains("ENABLED_BLOCKCHAIN")
|
||||
coVerify { localTogglesStorage.store(match { it["ENABLED_BLOCKCHAIN"] == false }) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,13 +203,26 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class IsMatchLocalConfig {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { excludedBlockchainTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, excludedBlockchainTogglesProvider, localTogglesStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isMatchLocalConfig_returnsTrueIfMatchesFile() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
every { versionProvider.get() } returns testVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage)
|
||||
val manager = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = manager.isMatchLocalConfig()
|
||||
|
|
@ -158,13 +234,17 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@Test
|
||||
fun isMatchLocalConfig_returnsFalseIfDiffersFromFile() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
every { versionProvider.get() } returns testVersion
|
||||
val fileToggles = getExpectedFileToggles(testVersion)
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage)
|
||||
manager.excludeBlockchain("CHAIN_1", false)
|
||||
val manager = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
)
|
||||
|
||||
// Act
|
||||
manager.excludeBlockchain("ENABLED_BLOCKCHAIN", !fileToggles.getValue("ENABLED_BLOCKCHAIN"))
|
||||
val actual = manager.isMatchLocalConfig()
|
||||
|
||||
// Assert
|
||||
|
|
@ -176,28 +256,36 @@ internal class DevExcludedBlockchainsManagerTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class RecoverLocalConfig {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { excludedBlockchainTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, excludedBlockchainTogglesProvider, localTogglesStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recoverLocalConfig_resetsToFileAndSaves() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
val toggles = mapOf("CHAIN_1" to "2.0.0", "CHAIN_2" to "2.0.0")
|
||||
mockkObject(ExcludedBlockchainToggles)
|
||||
every { ExcludedBlockchainToggles.values } returns toggles
|
||||
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns mapOf("CHAIN_1" to true)
|
||||
|
||||
val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage)
|
||||
manager.recoverLocalConfig()
|
||||
val appVersion = testVersion
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
val manager = DevExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
localTogglesStorage,
|
||||
)
|
||||
manager.excludeBlockchain("ENABLED_BLOCKCHAIN", false)
|
||||
|
||||
// Act
|
||||
manager.recoverLocalConfig()
|
||||
val actual = manager.excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).containsExactly("CHAIN_1", "CHAIN_2")
|
||||
|
||||
unmockkObject(ExcludedBlockchainToggles)
|
||||
clearMocks(versionProvider, localTogglesStorage)
|
||||
val expected = getExcludedBlockchains(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
package com.tangem.core.configtoggle.blockchain.impl
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.ExcludedBlockchainToggles
|
||||
import com.tangem.core.configtoggle.blockchain.provider.ExcludedBlockchainTogglesProvider
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import io.mockk.*
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.*
|
||||
|
||||
|
|
@ -11,39 +14,65 @@ import org.junit.jupiter.api.*
|
|||
internal class ProdExcludedBlockchainsManagerTest {
|
||||
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val excludedBlockchainTogglesProvider = mockk<ExcludedBlockchainTogglesProvider>()
|
||||
|
||||
private val testVersion = "5.30"
|
||||
|
||||
private val testToggles = mapOf(
|
||||
"ENABLED_BLOCKCHAIN" to "5.21.0",
|
||||
"DISABLED_BLOCKCHAIN" to "undefined",
|
||||
"FUTURE_BLOCKCHAIN" to "6.0.0",
|
||||
)
|
||||
|
||||
private fun getExcludedBlockchains(appVersion: String?): Set<String> {
|
||||
if (appVersion.isNullOrEmpty()) {
|
||||
return testToggles.keys
|
||||
}
|
||||
return testToggles.filterValues { version ->
|
||||
version == "undefined" || !isVersionSufficient(appVersion, version)
|
||||
}.keys
|
||||
}
|
||||
|
||||
private fun isVersionSufficient(appVersion: String, requiredVersion: String): Boolean {
|
||||
val appParts = appVersion.split(".").mapNotNull { it.toIntOrNull() }
|
||||
val reqParts = requiredVersion.split(".").mapNotNull { it.toIntOrNull() }
|
||||
for (i in 0 until maxOf(appParts.size, reqParts.size)) {
|
||||
val app = appParts.getOrElse(i) { 0 }
|
||||
val req = reqParts.getOrElse(i) { 0 }
|
||||
if (app > req) return true
|
||||
if (app < req) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Initialization {
|
||||
|
||||
@BeforeAll
|
||||
fun setupAll() {
|
||||
val toggles = mapOf("CHAIN_1" to "1.0.0", "CHAIN_2" to "2.0.0")
|
||||
mockkObject(ExcludedBlockchainToggles)
|
||||
every { ExcludedBlockchainToggles.values } returns toggles
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDownAll() {
|
||||
unmockkObject(ExcludedBlockchainToggles)
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { excludedBlockchainTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider)
|
||||
clearMocks(versionProvider, excludedBlockchainTogglesProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successfully initialize excluded blockchains`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = "1.0.0"
|
||||
val appVersion = testVersion
|
||||
every { versionProvider.get() } returns appVersion
|
||||
|
||||
// Act
|
||||
val actual = ProdExcludedBlockchainsManager(versionProvider).excludedBlockchainsIds
|
||||
val actual = ProdExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
).excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
val expected = setOf("CHAIN_2")
|
||||
val expected = getExcludedBlockchains(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
|
||||
coVerifyOrder { versionProvider.get() }
|
||||
|
|
@ -52,13 +81,17 @@ internal class ProdExcludedBlockchainsManagerTest {
|
|||
@Test
|
||||
fun `all blockchains excluded if versionProvider returns null`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns null
|
||||
val appVersion: String? = null
|
||||
every { versionProvider.get() } returns appVersion
|
||||
|
||||
// Act
|
||||
val actual = ProdExcludedBlockchainsManager(versionProvider).excludedBlockchainsIds
|
||||
val actual = ProdExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
).excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
val expected = setOf("CHAIN_1", "CHAIN_2")
|
||||
val expected = getExcludedBlockchains(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
|
||||
coVerifyOrder { versionProvider.get() }
|
||||
|
|
@ -67,13 +100,17 @@ internal class ProdExcludedBlockchainsManagerTest {
|
|||
@Test
|
||||
fun `all blockchains excluded if versionProvider returns empty string`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns ""
|
||||
val appVersion = ""
|
||||
every { versionProvider.get() } returns appVersion
|
||||
|
||||
// Act
|
||||
val actual = ProdExcludedBlockchainsManager(versionProvider).excludedBlockchainsIds
|
||||
val actual = ProdExcludedBlockchainsManager(
|
||||
versionProvider,
|
||||
excludedBlockchainTogglesProvider,
|
||||
).excludedBlockchainsIds
|
||||
|
||||
// Assert
|
||||
val expected = setOf("CHAIN_1", "CHAIN_2")
|
||||
val expected = getExcludedBlockchains(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
|
||||
coVerifyOrder { versionProvider.get() }
|
||||
|
|
@ -86,7 +123,9 @@ internal class ProdExcludedBlockchainsManagerTest {
|
|||
every { versionProvider.get() } throws exception
|
||||
|
||||
// Act
|
||||
val actual = runCatching { ProdExcludedBlockchainsManager(versionProvider) }.exceptionOrNull()!!
|
||||
val actual = runCatching {
|
||||
ProdExcludedBlockchainsManager(versionProvider, excludedBlockchainTogglesProvider)
|
||||
}.exceptionOrNull()!!
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isInstanceOf(exception::class.java)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package com.tangem.core.configtoggle.manager
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.manager.ProdFeatureTogglesManagerTest.IsFeatureEnabledModel
|
||||
import com.tangem.core.configtoggle.feature.provider.FeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
|
|
@ -12,51 +11,55 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DevFeatureTogglesManagerTest {
|
||||
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val featureTogglesProvider = mockk<FeatureTogglesProvider>()
|
||||
private val featureTogglesLocalStorage = mockk<LocalTogglesStorage>(relaxUnitFun = true)
|
||||
|
||||
private val testToggles = mapOf(
|
||||
"ENABLED_TOGGLE" to "1.0.0",
|
||||
"DISABLED_TOGGLE" to "undefined",
|
||||
)
|
||||
|
||||
private fun getExpectedFileToggles(appVersion: String?): Map<String, Boolean> =
|
||||
testToggles.mapValues { (_, version) ->
|
||||
version != "undefined" && !appVersion.isNullOrEmpty()
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Initialization {
|
||||
|
||||
@BeforeAll
|
||||
fun setupAll() {
|
||||
val featureToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "2.0.0")
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns featureToggles
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDownAll() {
|
||||
unmockkObject(FeatureToggles)
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, featureTogglesLocalStorage)
|
||||
clearMocks(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successfully initialize manager`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = "1.0.0"
|
||||
val savedFeatureToggles = mapOf("TOGGLE_1" to false, "TOGGLE_2" to true)
|
||||
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val savedToggles = fileToggles.mapValues { !it.value }
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedToggles
|
||||
|
||||
// Act
|
||||
val actual = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage).getFeatureToggles()
|
||||
val actual = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = savedFeatureToggles
|
||||
val expected = savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -68,17 +71,21 @@ internal class DevFeatureTogglesManagerTest {
|
|||
@Test
|
||||
fun `successfully initialize manager if versionProvider returns null`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = null
|
||||
val savedFeatureToggles = mapOf("TOGGLE_1" to false, "TOGGLE_2" to true)
|
||||
|
||||
val appVersion: String? = null
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val savedToggles = fileToggles.mapValues { !it.value }
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedToggles
|
||||
|
||||
// Act
|
||||
val actual = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage).getFeatureToggles()
|
||||
val actual = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = savedFeatureToggles
|
||||
val expected = savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -91,16 +98,20 @@ internal class DevFeatureTogglesManagerTest {
|
|||
fun `successfully initialize manager if versionProvider returns empty string`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = ""
|
||||
val savedFeatureToggles = mapOf("TOGGLE_1" to false, "TOGGLE_2" to true)
|
||||
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val savedToggles = fileToggles.mapValues { !it.value }
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedToggles
|
||||
|
||||
// Act
|
||||
val actual = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage).getFeatureToggles()
|
||||
val actual = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = savedFeatureToggles
|
||||
val expected = savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -116,8 +127,13 @@ internal class DevFeatureTogglesManagerTest {
|
|||
every { versionProvider.get() } throws exception
|
||||
|
||||
// Act
|
||||
val actual = runCatching { DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage) }
|
||||
.exceptionOrNull()!!
|
||||
val actual = runCatching {
|
||||
DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
)
|
||||
}.exceptionOrNull()!!
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isInstanceOf(exception::class.java)
|
||||
|
|
@ -131,16 +147,18 @@ internal class DevFeatureTogglesManagerTest {
|
|||
fun `successfully initialize manager if storage returns empty map`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = "1.0.0"
|
||||
val savedFeatureToggles = emptyMap<String, Boolean>()
|
||||
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
// Act
|
||||
val actual = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage).getFeatureToggles()
|
||||
val actual = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = mapOf("TOGGLE_1" to true, "TOGGLE_2" to false)
|
||||
val expected = getExpectedFileToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -153,16 +171,20 @@ internal class DevFeatureTogglesManagerTest {
|
|||
fun `successfully initialize manager if storage returns unknown toggles`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = "1.0.0"
|
||||
val savedFeatureToggles = mapOf("TOGGLE_3" to true)
|
||||
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val unknownSavedToggles = fileToggles + ("UNKNOWN_TOGGLE" to true)
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns unknownSavedToggles
|
||||
|
||||
// Act
|
||||
val actual = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage).getFeatureToggles()
|
||||
val actual = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = mapOf("TOGGLE_1" to true, "TOGGLE_2" to false)
|
||||
val expected = fileToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -176,13 +198,17 @@ internal class DevFeatureTogglesManagerTest {
|
|||
// Arrange
|
||||
val appVersion = "1.0.0"
|
||||
val exception = Exception("Test exception")
|
||||
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } throws exception
|
||||
|
||||
// Act
|
||||
val actual = runCatching { DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage) }
|
||||
.exceptionOrNull()!!
|
||||
val actual = runCatching {
|
||||
DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
)
|
||||
}.exceptionOrNull()!!
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isInstanceOf(exception::class.java)
|
||||
|
|
@ -203,29 +229,19 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
@BeforeAll
|
||||
fun setupAll() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
val featureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to "undefined",
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to "1.0.0",
|
||||
val savedToggles = mapOf(
|
||||
"DISABLED_TOGGLE" to true,
|
||||
"ENABLED_TOGGLE" to false,
|
||||
)
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns featureToggles
|
||||
|
||||
val savedFeatureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to true,
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to false,
|
||||
)
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
|
||||
manager = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage)
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedToggles
|
||||
manager = DevFeatureTogglesManager(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDownAll() {
|
||||
clearMocks(versionProvider, featureTogglesLocalStorage)
|
||||
unmockkObject(FeatureToggles)
|
||||
clearMocks(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
|
@ -235,33 +251,42 @@ internal class DevFeatureTogglesManagerTest {
|
|||
val actual = manager.isFeatureEnabled(name = model.name)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
IsFeatureEnabledModel(name = "ACTIVE2_TEST_FEATURE_ENABLED", expected = false),
|
||||
IsFeatureEnabledModel(name = "INACTIVE_TEST_FEATURE_ENABLED", expected = true),
|
||||
IsFeatureEnabledModel(name = "UNKNOWN_FEATURE_ENABLED", expected = false),
|
||||
IsFeatureEnabledModel(name = "", expected = false),
|
||||
IsFeatureEnabledModel(name = "ENABLED_TOGGLE", expected = false),
|
||||
IsFeatureEnabledModel(name = "DISABLED_TOGGLE", expected = true),
|
||||
)
|
||||
}
|
||||
|
||||
data class IsFeatureEnabledModel(val name: String, val expected: Boolean)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class IsMatchLocalConfig {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun isMatchLocalConfig(model: IsMatchLocalConfigModel) = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns model.fileFeatureToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage).apply {
|
||||
val manager = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).apply {
|
||||
setFeatureToggles(model.storedFeatureToggles.toMutableMap())
|
||||
}
|
||||
|
||||
|
|
@ -269,54 +294,45 @@ internal class DevFeatureTogglesManagerTest {
|
|||
val actual = manager.isMatchLocalConfig()
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
featureTogglesLocalStorage.getSyncOrEmpty()
|
||||
}
|
||||
|
||||
clearMocks(versionProvider, featureTogglesLocalStorage)
|
||||
unmockkObject(FeatureToggles)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
IsMatchLocalConfigModel(
|
||||
fileFeatureToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "2.0.0"),
|
||||
storedFeatureToggles = mapOf("TOGGLE_1" to true, "TOGGLE_2" to false),
|
||||
expected = true,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
fileFeatureToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "2.0.0"),
|
||||
storedFeatureToggles = mapOf("TOGGLE_1" to true, "TOGGLE_2" to true),
|
||||
expected = false,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
fileFeatureToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "2.0.0"),
|
||||
storedFeatureToggles = mapOf("TOGGLE_1" to true),
|
||||
expected = false,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
fileFeatureToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "2.0.0"),
|
||||
storedFeatureToggles = mapOf("TOGGLE_3" to true, "TOGGLE_4" to false),
|
||||
expected = false,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
fileFeatureToggles = emptyMap(),
|
||||
storedFeatureToggles = emptyMap(),
|
||||
expected = true,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
fileFeatureToggles = emptyMap(),
|
||||
storedFeatureToggles = mapOf("TOGGLE_1" to true),
|
||||
expected = false,
|
||||
),
|
||||
)
|
||||
private fun provideTestModels(): List<IsMatchLocalConfigModel> {
|
||||
val appVersion = "1.0.0"
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val firstToggleName = "ENABLED_TOGGLE"
|
||||
|
||||
return listOf(
|
||||
IsMatchLocalConfigModel(
|
||||
storedFeatureToggles = fileToggles,
|
||||
expected = true,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
storedFeatureToggles = fileToggles.mapValues { !it.value },
|
||||
expected = false,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
storedFeatureToggles = mapOf(firstToggleName to fileToggles.getValue(firstToggleName)),
|
||||
expected = false,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
storedFeatureToggles = mapOf("UNKNOWN_TOGGLE" to true),
|
||||
expected = false,
|
||||
),
|
||||
IsMatchLocalConfigModel(
|
||||
storedFeatureToggles = emptyMap(),
|
||||
expected = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class IsMatchLocalConfigModel(
|
||||
val fileFeatureToggles: Map<String, String>,
|
||||
val storedFeatureToggles: Map<String, Boolean>,
|
||||
val expected: Boolean,
|
||||
)
|
||||
|
|
@ -325,41 +341,40 @@ internal class DevFeatureTogglesManagerTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class GetFeatureToggles {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getFeatureToggles() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
val fileFeatureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to "undefined",
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to "1.0.0",
|
||||
val appVersion = "1.0.0"
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val savedToggles = mapOf(
|
||||
"DISABLED_TOGGLE" to true,
|
||||
"ENABLED_TOGGLE" to false,
|
||||
)
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns fileFeatureToggles
|
||||
|
||||
val savedFeatureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to true,
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to false,
|
||||
)
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage)
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedToggles
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
|
||||
// Act
|
||||
val actual = manager.getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = savedFeatureToggles
|
||||
val expected = fileToggles + savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
featureTogglesLocalStorage.getSyncOrEmpty()
|
||||
}
|
||||
|
||||
clearMocks(versionProvider, featureTogglesLocalStorage)
|
||||
unmockkObject(FeatureToggles)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,25 +382,30 @@ internal class DevFeatureTogglesManagerTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ChangeToggle {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun changeToggle(model: ChangeToggleModel) = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns model.initialToggles
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage)
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
|
||||
// Act
|
||||
manager.changeToggle(name = model.name, isEnabled = model.isEnabled)
|
||||
val actual = manager.getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = model.expectedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(model.expectedToggles)
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -395,77 +415,69 @@ internal class DevFeatureTogglesManagerTest {
|
|||
featureTogglesLocalStorage.store(model.expectedToggles)
|
||||
}
|
||||
}
|
||||
|
||||
clearMocks(versionProvider, featureTogglesLocalStorage)
|
||||
unmockkObject(FeatureToggles)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
ChangeToggleModel(
|
||||
initialToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "undefined"),
|
||||
name = "TOGGLE_1",
|
||||
isEnabled = false,
|
||||
expectedToggles = mapOf("TOGGLE_1" to false, "TOGGLE_2" to false),
|
||||
expectedStoreSaving = true,
|
||||
),
|
||||
ChangeToggleModel(
|
||||
initialToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "undefined"),
|
||||
name = "TOGGLE_2",
|
||||
isEnabled = true,
|
||||
expectedToggles = mapOf("TOGGLE_1" to true, "TOGGLE_2" to true),
|
||||
expectedStoreSaving = true,
|
||||
),
|
||||
ChangeToggleModel(
|
||||
initialToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "undefined"),
|
||||
name = "TOGGLE_3",
|
||||
isEnabled = true,
|
||||
expectedToggles = mapOf("TOGGLE_1" to true, "TOGGLE_2" to false),
|
||||
expectedStoreSaving = false,
|
||||
),
|
||||
ChangeToggleModel(
|
||||
initialToggles = emptyMap(),
|
||||
name = "TOGGLE_1",
|
||||
isEnabled = true,
|
||||
expectedToggles = emptyMap(),
|
||||
expectedStoreSaving = false,
|
||||
),
|
||||
)
|
||||
private fun provideTestModels(): List<ChangeToggleModel> {
|
||||
val appVersion = "1.0.0"
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
val enabledName = "ENABLED_TOGGLE"
|
||||
val disabledName = "DISABLED_TOGGLE"
|
||||
|
||||
return listOf(
|
||||
ChangeToggleModel(
|
||||
name = enabledName,
|
||||
isEnabled = false,
|
||||
expectedToggles = fileToggles + (enabledName to false),
|
||||
expectedStoreSaving = true,
|
||||
),
|
||||
ChangeToggleModel(
|
||||
name = disabledName,
|
||||
isEnabled = true,
|
||||
expectedToggles = fileToggles + (disabledName to true),
|
||||
expectedStoreSaving = true,
|
||||
),
|
||||
ChangeToggleModel(
|
||||
name = "UNKNOWN_TOGGLE",
|
||||
isEnabled = true,
|
||||
expectedToggles = fileToggles,
|
||||
expectedStoreSaving = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class RecoverLocalConfig {
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recoverLocalConfig() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
val fileFeatureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to "undefined",
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to "1.0.0",
|
||||
val appVersion = "1.0.0"
|
||||
val savedToggles = mapOf(
|
||||
"DISABLED_TOGGLE" to true,
|
||||
"ENABLED_TOGGLE" to false,
|
||||
)
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns fileFeatureToggles
|
||||
|
||||
val savedFeatureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to true,
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to false,
|
||||
)
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedFeatureToggles
|
||||
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesLocalStorage)
|
||||
every { versionProvider.get() } returns appVersion
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns savedToggles
|
||||
val manager = DevFeatureTogglesManager(versionProvider, featureTogglesProvider, featureTogglesLocalStorage)
|
||||
|
||||
// Act
|
||||
manager.recoverLocalConfig()
|
||||
val actual = manager.getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to false,
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to true,
|
||||
)
|
||||
val expected = getExpectedFileToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
|
|
@ -473,14 +485,10 @@ internal class DevFeatureTogglesManagerTest {
|
|||
featureTogglesLocalStorage.getSyncOrEmpty()
|
||||
featureTogglesLocalStorage.store(expected)
|
||||
}
|
||||
|
||||
clearMocks(versionProvider, featureTogglesLocalStorage)
|
||||
unmockkObject(FeatureToggles)
|
||||
}
|
||||
}
|
||||
|
||||
data class ChangeToggleModel(
|
||||
val initialToggles: Map<String, String>,
|
||||
val name: String,
|
||||
val isEnabled: Boolean,
|
||||
val expectedToggles: Map<String, Boolean>,
|
||||
|
|
|
|||
|
|
@ -1,43 +1,47 @@
|
|||
package com.tangem.core.configtoggle.manager
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.impl.ProdFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.provider.FeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import io.mockk.*
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class ProdFeatureTogglesManagerTest {
|
||||
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val featureTogglesProvider = mockk<FeatureTogglesProvider>()
|
||||
|
||||
private val testToggles = mapOf(
|
||||
"ENABLED_TOGGLE" to "1.0.0",
|
||||
"DISABLED_TOGGLE" to "undefined",
|
||||
)
|
||||
|
||||
private fun getExpectedToggles(appVersion: String?): Map<String, Boolean> {
|
||||
return testToggles.mapValues { (_, version) ->
|
||||
version != "undefined" && !appVersion.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Initialization {
|
||||
|
||||
@BeforeAll
|
||||
fun setupAll() {
|
||||
val featureToggles = mapOf("TOGGLE_1" to "1.0.0", "TOGGLE_2" to "2.0.0")
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns featureToggles
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDownAll() {
|
||||
unmockkObject(FeatureToggles)
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(versionProvider)
|
||||
clearMocks(versionProvider, featureTogglesProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -47,10 +51,10 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
every { versionProvider.get() } returns appVersion
|
||||
|
||||
// Act
|
||||
val actual = ProdFeatureTogglesManager(versionProvider).getProdFeatureToggles()
|
||||
val actual = ProdFeatureTogglesManager(versionProvider, featureTogglesProvider).getProdFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = mapOf("TOGGLE_1" to true, "TOGGLE_2" to false)
|
||||
val expected = getExpectedToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder { versionProvider.get() }
|
||||
|
|
@ -59,14 +63,14 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
@Test
|
||||
fun `successfully initialize storage if versionProvider returns null`() = runTest {
|
||||
// Arrange
|
||||
val appVersion = null
|
||||
val appVersion: String? = null
|
||||
every { versionProvider.get() } returns appVersion
|
||||
|
||||
// Act
|
||||
val actual = ProdFeatureTogglesManager(versionProvider).getProdFeatureToggles()
|
||||
val actual = ProdFeatureTogglesManager(versionProvider, featureTogglesProvider).getProdFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = mapOf("TOGGLE_1" to false, "TOGGLE_2" to false)
|
||||
val expected = getExpectedToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder { versionProvider.get() }
|
||||
|
|
@ -79,10 +83,10 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
every { versionProvider.get() } returns appVersion
|
||||
|
||||
// Act
|
||||
val actual = ProdFeatureTogglesManager(versionProvider).getProdFeatureToggles()
|
||||
val actual = ProdFeatureTogglesManager(versionProvider, featureTogglesProvider).getProdFeatureToggles()
|
||||
|
||||
// Assert
|
||||
val expected = mapOf("TOGGLE_1" to false, "TOGGLE_2" to false)
|
||||
val expected = getExpectedToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
||||
coVerifyOrder { versionProvider.get() }
|
||||
|
|
@ -95,7 +99,8 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
every { versionProvider.get() } throws exception
|
||||
|
||||
// Act
|
||||
val actual = runCatching { ProdFeatureTogglesManager(versionProvider) }.exceptionOrNull()!!
|
||||
val actual = runCatching { ProdFeatureTogglesManager(versionProvider, featureTogglesProvider) }
|
||||
.exceptionOrNull()!!
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isInstanceOf(exception::class.java)
|
||||
|
|
@ -113,23 +118,14 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
|
||||
@BeforeAll
|
||||
fun setupAll() {
|
||||
every { featureTogglesProvider.getToggles() } returns testToggles
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
|
||||
val featureToggles = mapOf(
|
||||
"INACTIVE_TEST_FEATURE_ENABLED" to "undefined",
|
||||
"ACTIVE2_TEST_FEATURE_ENABLED" to "1.0.0",
|
||||
)
|
||||
|
||||
mockkObject(FeatureToggles)
|
||||
every { FeatureToggles.values } returns featureToggles
|
||||
|
||||
manager = ProdFeatureTogglesManager(versionProvider)
|
||||
manager = ProdFeatureTogglesManager(versionProvider, featureTogglesProvider)
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDownAll() {
|
||||
clearMocks(versionProvider)
|
||||
unmockkObject(FeatureToggles)
|
||||
clearMocks(versionProvider, featureTogglesProvider)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
|
@ -139,15 +135,12 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
val actual = manager.isFeatureEnabled(name = model.name)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
IsFeatureEnabledModel(name = "ACTIVE2_TEST_FEATURE_ENABLED", expected = true),
|
||||
IsFeatureEnabledModel(name = "INACTIVE_TEST_FEATURE_ENABLED", expected = false),
|
||||
IsFeatureEnabledModel(name = "UNKNOWN_FEATURE_ENABLED", expected = false),
|
||||
IsFeatureEnabledModel(name = "", expected = false),
|
||||
IsFeatureEnabledModel(name = "ENABLED_TOGGLE", expected = true),
|
||||
IsFeatureEnabledModel(name = "DISABLED_TOGGLE", expected = false),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.plugin.configuration.configurations
|
||||
|
||||
import com.squareup.kotlinpoet.*
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
|
|
@ -12,8 +11,7 @@ import java.util.Locale
|
|||
/**
|
||||
* Generator for toggle configuration Kotlin enum classes from JSON files.
|
||||
* Parses a JSON array of `{ "name": "...", "version": "..." }` entries
|
||||
* and generates an enum class with an entry for each toggle and a `values: Map<String, String>`
|
||||
* in companion object mapping toggle name to its version string.
|
||||
* and generates an enum class with `rawName` and `version` properties for each toggle.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -39,31 +37,35 @@ object TogglesGenerator {
|
|||
ToggleEntry(name = name, enumName = name.toEnumEntryName(), version = version)
|
||||
}
|
||||
|
||||
val mapInitializer = buildCodeBlock {
|
||||
addStatement("mapOf(")
|
||||
withIndent {
|
||||
entries.forEach { entry ->
|
||||
addStatement("%S to %S,", entry.name, entry.version)
|
||||
}
|
||||
}
|
||||
add(")")
|
||||
}
|
||||
|
||||
val companionBuilder = TypeSpec.companionObjectBuilder()
|
||||
.addProperty(
|
||||
PropertySpec.builder("values", MAP.parameterizedBy(STRING, STRING))
|
||||
.initializer(mapInitializer)
|
||||
.build(),
|
||||
)
|
||||
|
||||
val enumBuilder = TypeSpec.enumBuilder(fileName)
|
||||
.addKdoc("Generated from ${inputFile.name}\nAuto-generated - do not edit manually.")
|
||||
.primaryConstructor(
|
||||
FunSpec.constructorBuilder()
|
||||
.addParameter(ParameterSpec.builder("rawName", String::class).build())
|
||||
.addParameter(ParameterSpec.builder("version", String::class).build())
|
||||
.build(),
|
||||
)
|
||||
.addProperty(
|
||||
PropertySpec.builder("rawName", String::class)
|
||||
.initializer("rawName")
|
||||
.build(),
|
||||
)
|
||||
.addProperty(
|
||||
PropertySpec.builder("version", String::class)
|
||||
.initializer("version")
|
||||
.build(),
|
||||
)
|
||||
.apply {
|
||||
entries.forEach { entry ->
|
||||
addEnumConstant(entry.enumName)
|
||||
addEnumConstant(
|
||||
entry.enumName,
|
||||
TypeSpec.anonymousClassBuilder()
|
||||
.addSuperclassConstructorParameter("%S", entry.name)
|
||||
.addSuperclassConstructorParameter("%S", entry.version)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
.addType(companionBuilder.build())
|
||||
|
||||
val fileSpec = FileSpec.builder(packageName = PACKAGE_NAME, fileName = fileName)
|
||||
.indent(" ")
|
||||
|
|
@ -73,7 +75,6 @@ object TogglesGenerator {
|
|||
outputDir.mkdirs()
|
||||
fileSpec.writeTo(outputDir)
|
||||
|
||||
// Remove redundant public visibility modifiers
|
||||
val generatedFile = File(outputDir, "${PACKAGE_NAME.replace('.', '/')}/$fileName.kt")
|
||||
if (generatedFile.exists()) {
|
||||
val content = generatedFile.readText()
|
||||
|
|
@ -81,6 +82,7 @@ object TogglesGenerator {
|
|||
.replace("public enum class ", "enum class ")
|
||||
.replace("public companion object", "companion object")
|
||||
.replace("public val ", "val ")
|
||||
.replace(Regex(""",\s*;\s*\n\s*}"""), ",\n}")
|
||||
generatedFile.writeText(fixedContent)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,25 @@ class TogglesGeneratorTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("enum class FeatureToggles {")
|
||||
assertThat(code).contains("enum class FeatureToggles(")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate creates enum entries and companion object`() {
|
||||
fun `generate creates enum with constructor parameters`() {
|
||||
// Arrange
|
||||
val json = """[{ "name": "onramp/ios", "version": "1.0.0" }]"""
|
||||
|
||||
// Act
|
||||
val code = generateAndReadOutput(json, "Toggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("ONRAMP_IOS(")
|
||||
assertThat(code).contains("val rawName: String")
|
||||
assertThat(code).contains("val version: String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate creates enum entries without companion object`() {
|
||||
// Arrange & Act
|
||||
val code = generateAndReadOutput(
|
||||
json = SINGLE_ENTRY_JSON,
|
||||
|
|
@ -54,21 +68,23 @@ class TogglesGeneratorTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("STAKING_ETH_ENABLED,")
|
||||
assertThat(code).contains("companion object {")
|
||||
assertThat(code).contains("STAKING_ETH_ENABLED(")
|
||||
assertThat(code).contains("val rawName: String")
|
||||
assertThat(code).contains("val version: String")
|
||||
assertThat(code).doesNotContain("companion object")
|
||||
assertThat(code).doesNotContain("val values:")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate creates values map with string literal keys`() {
|
||||
fun `generate creates enum entries with constructor arguments`() {
|
||||
// Arrange & Act
|
||||
val code = generateAndReadOutput(
|
||||
json = SINGLE_ENTRY_JSON,
|
||||
fileName = "FeatureToggles",
|
||||
)
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("val values: Map<String, String> = mapOf(")
|
||||
assertThat(code).contains(""""STAKING_ETH_ENABLED" to "undefined"""")
|
||||
// Assert - verify enum entry includes constructor arguments
|
||||
assertThat(code).contains(""""STAKING_ETH_ENABLED", "undefined"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -86,12 +102,12 @@ class TogglesGeneratorTest {
|
|||
val code = generateAndReadOutput(json, "TestToggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("FEATURE_A,")
|
||||
assertThat(code).contains("FEATURE_B,")
|
||||
assertThat(code).contains("FEATURE_C,")
|
||||
assertThat(code).contains(""""FEATURE_A" to "1.0"""")
|
||||
assertThat(code).contains(""""FEATURE_B" to "2.0"""")
|
||||
assertThat(code).contains(""""FEATURE_C" to "undefined"""")
|
||||
assertThat(code).contains("FEATURE_A(")
|
||||
assertThat(code).contains("FEATURE_B(")
|
||||
assertThat(code).contains("FEATURE_C(")
|
||||
assertThat(code).contains(""""FEATURE_A", "1.0"""")
|
||||
assertThat(code).contains(""""FEATURE_B", "2.0"""")
|
||||
assertThat(code).contains(""""FEATURE_C", "undefined"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -103,8 +119,8 @@ class TogglesGeneratorTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("enum class EmptyToggles {")
|
||||
assertThat(code).contains("val values: Map<String, String> = mapOf(")
|
||||
assertThat(code).contains("enum class EmptyToggles(")
|
||||
assertThat(code).doesNotContain("companion object")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -116,8 +132,8 @@ class TogglesGeneratorTest {
|
|||
val code = generateAndReadOutput(json, "Toggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("NEXA_TEST,")
|
||||
assertThat(code).contains(""""NEXA/test" to "undefined"""")
|
||||
assertThat(code).contains("NEXA_TEST(")
|
||||
assertThat(code).contains(""""NEXA/test", "undefined"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -129,8 +145,8 @@ class TogglesGeneratorTest {
|
|||
val code = generateAndReadOutput(json, "Toggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("VANAR_CHAIN,")
|
||||
assertThat(code).contains(""""vanar-chain" to "undefined"""")
|
||||
assertThat(code).contains("VANAR_CHAIN(")
|
||||
assertThat(code).contains(""""vanar-chain", "undefined"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -142,8 +158,8 @@ class TogglesGeneratorTest {
|
|||
val code = generateAndReadOutput(json, "Toggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("SONIC,")
|
||||
assertThat(code).contains(""""sonic" to "5.21.0"""")
|
||||
assertThat(code).contains("SONIC(")
|
||||
assertThat(code).contains(""""sonic", "5.21.0"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -169,7 +185,6 @@ class TogglesGeneratorTest {
|
|||
|
||||
// Assert
|
||||
assertThat(code).doesNotContain("public enum class")
|
||||
assertThat(code).doesNotContain("public companion object")
|
||||
assertThat(code).doesNotContain("public val")
|
||||
}
|
||||
|
||||
|
|
@ -188,12 +203,12 @@ class TogglesGeneratorTest {
|
|||
val code = generateAndReadOutput(json, "FeatureToggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("NEW_CARD_SCANNING_ENABLED,")
|
||||
assertThat(code).contains("HOT_WALLET_CREATION_RESTRICTION_ENABLED,")
|
||||
assertThat(code).contains("SWAP_MARKET_LIST_ENABLED,")
|
||||
assertThat(code).contains(""""NEW_CARD_SCANNING_ENABLED" to "undefined"""")
|
||||
assertThat(code).contains(""""HOT_WALLET_CREATION_RESTRICTION_ENABLED" to "5.32.0"""")
|
||||
assertThat(code).contains(""""SWAP_MARKET_LIST_ENABLED" to "5.34"""")
|
||||
assertThat(code).contains("NEW_CARD_SCANNING_ENABLED(")
|
||||
assertThat(code).contains("HOT_WALLET_CREATION_RESTRICTION_ENABLED(")
|
||||
assertThat(code).contains("SWAP_MARKET_LIST_ENABLED(")
|
||||
assertThat(code).contains(""""NEW_CARD_SCANNING_ENABLED", "undefined"""")
|
||||
assertThat(code).contains(""""HOT_WALLET_CREATION_RESTRICTION_ENABLED", "5.32.0"""")
|
||||
assertThat(code).contains(""""SWAP_MARKET_LIST_ENABLED", "5.34"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -211,13 +226,15 @@ class TogglesGeneratorTest {
|
|||
val code = generateAndReadOutput(json, "ExcludedBlockchainToggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code).contains("enum class ExcludedBlockchainToggles {")
|
||||
assertThat(code).contains("NEXA,")
|
||||
assertThat(code).contains("NEXA_TEST,")
|
||||
assertThat(code).contains("SONIC,")
|
||||
assertThat(code).contains(""""NEXA" to "undefined"""")
|
||||
assertThat(code).contains(""""NEXA/test" to "undefined"""")
|
||||
assertThat(code).contains(""""sonic" to "5.21.0"""")
|
||||
assertThat(code).contains("enum class ExcludedBlockchainToggles(")
|
||||
assertThat(code).contains("val rawName: String")
|
||||
assertThat(code).contains("val version: String")
|
||||
assertThat(code).contains("NEXA(")
|
||||
assertThat(code).contains("NEXA_TEST(")
|
||||
assertThat(code).contains("SONIC(")
|
||||
assertThat(code).contains(""""NEXA", "undefined"""")
|
||||
assertThat(code).contains(""""NEXA/test", "undefined"""")
|
||||
assertThat(code).contains(""""sonic", "5.21.0"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -230,8 +247,25 @@ class TogglesGeneratorTest {
|
|||
val code2 = generateAndReadOutput(json, "ExcludedBlockchainToggles")
|
||||
|
||||
// Assert
|
||||
assertThat(code1).contains("enum class FeatureToggles {")
|
||||
assertThat(code2).contains("enum class ExcludedBlockchainToggles {")
|
||||
assertThat(code1).contains("enum class FeatureToggles(")
|
||||
assertThat(code2).contains("enum class ExcludedBlockchainToggles(")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate does not include semicolon after enum entries`() {
|
||||
// Arrange
|
||||
val json = """
|
||||
[
|
||||
{ "name": "FEATURE_A", "version": "1.0" },
|
||||
{ "name": "FEATURE_B", "version": "2.0" }
|
||||
]
|
||||
""".trimIndent()
|
||||
|
||||
// Act
|
||||
val code = generateAndReadOutput(json, "TestToggles")
|
||||
|
||||
// Assert - ensure no trailing semicolons
|
||||
assertThat(code).doesNotContain(";\n")
|
||||
}
|
||||
|
||||
private fun generateAndReadOutput(json: String, fileName: String): String {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue