diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index a413d65b34..ebad984a1a 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -293,16 +293,13 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat Timber.i("APP STARTED") if (BuildConfig.TESTER_MENU_ENABLED) { Timber.i(featureTogglesManager.toString()) + Timber.i(excludedBlockchainsManager.toString()) } foregroundActivityObserver = ForegroundActivityObserver() registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) - // We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them. runBlocking { - awaitAll( - async { excludedBlockchainsManager.init() }, - ) initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) } diff --git a/core/config-toggles/build.gradle.kts b/core/config-toggles/build.gradle.kts index 368af3bc77..9b23b82a3c 100644 --- a/core/config-toggles/build.gradle.kts +++ b/core/config-toggles/build.gradle.kts @@ -24,7 +24,7 @@ android { } tasks.named("preBuild") { - dependsOn(generateFeatureToggles /*generateExcludedBlockchainToggles*/) + dependsOn(generateFeatureToggles, generateExcludedBlockchainToggles) } tasks.withType().configureEach { diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/ExcludedBlockchainsManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/ExcludedBlockchainsManager.kt index f5db3637b9..4ff86959b7 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/ExcludedBlockchainsManager.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/ExcludedBlockchainsManager.kt @@ -3,6 +3,4 @@ package com.tangem.core.configtoggle.blockchain interface ExcludedBlockchainsManager { val excludedBlockchainsIds: Set - - suspend fun init() } \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt deleted file mode 100644 index 0a260230e9..0000000000 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.tangem.core.configtoggle.blockchain.impl - -import com.tangem.core.configtoggle.blockchain.MutableExcludedBlockchainsManager -import com.tangem.core.configtoggle.storage.TogglesStorage -import com.tangem.core.configtoggle.utils.associateToggles -import com.tangem.core.configtoggle.version.VersionProvider -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getObjectMapSync -import com.tangem.datasource.local.preferences.utils.storeObjectMap - -internal class DefaultExcludedBlockchainsManager( - private val localTogglesStorage: TogglesStorage, - private val appPreferencesStore: AppPreferencesStore, - private val versionProvider: VersionProvider, -) : MutableExcludedBlockchainsManager { - - private var isInitialized: Boolean = false - - private lateinit var currentExcludedBlockchains: MutableMap - private lateinit var localExcludedBlockchains: Map - - override val excludedBlockchainsIds: Set - get() { - if (!isInitialized) error("ExcludedBlockchainsManager is not initialized") - - return currentExcludedBlockchains - .filterValues { it } - .keys - } - - override suspend fun init() { - localTogglesStorage.populate(path = "configs/excluded_blockchains_config") - - val storedExcludedBlockchainsIds = appPreferencesStore.getObjectMapSync( - key = PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY, - ) - - localExcludedBlockchains = localTogglesStorage.toggles - .associateToggles(currentVersion = versionProvider.get().orEmpty()) - .mapValues { (_, isIncluded) -> !isIncluded } - - currentExcludedBlockchains = (localExcludedBlockchains.keys + storedExcludedBlockchainsIds.keys) - .fold(mutableMapOf()) { acc, blockchainId -> - val isExcluded = storedExcludedBlockchainsIds[blockchainId] ?: localExcludedBlockchains[blockchainId] - - requireNotNull(isExcluded) { - "Unable to find $blockchainId in local or stored excluded blockchains" - } - - acc[blockchainId] = isExcluded - acc - } - - isInitialized = true - } - - override suspend fun excludeBlockchain(mainnetId: String, isExcluded: Boolean) { - currentExcludedBlockchains[mainnetId] = isExcluded - - storeCurrent() - } - - override fun isMatchLocalConfig(): Boolean { - return currentExcludedBlockchains == localExcludedBlockchains - } - - override suspend fun recoverLocalConfig() { - currentExcludedBlockchains = localExcludedBlockchains.toMutableMap() - - storeCurrent() - } - - private suspend fun storeCurrent() { - appPreferencesStore.storeObjectMap( - key = PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY, - value = currentExcludedBlockchains, - ) - } -} \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt new file mode 100644 index 0000000000..6f6645c865 --- /dev/null +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt @@ -0,0 +1,66 @@ +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.storage.LocalTogglesStorage +import com.tangem.core.configtoggle.utils.defineTogglesAvailability +import com.tangem.core.configtoggle.utils.toTableString +import com.tangem.core.configtoggle.version.VersionProvider +import kotlinx.coroutines.runBlocking +import kotlin.properties.Delegates + +/** + * [MutableExcludedBlockchainsManager] implementation in dev or mocked build + * + * @property versionProvider application version provider + * @property localTogglesStorage local storage for blockchain toggles + */ +internal class DevExcludedBlockchainsManager( + private val versionProvider: VersionProvider, + private val localTogglesStorage: LocalTogglesStorage, +) : MutableExcludedBlockchainsManager { + + private val fileBlockchainToggles: Map = getFileBlockchainToggles() + private var blockchainTogglesMap: MutableMap by Delegates.notNull() + + override val excludedBlockchainsIds: Set + get() = blockchainTogglesMap.filterValues { !it }.keys + + init { + val savedExcludedBlockchains = runBlocking { localTogglesStorage.getSyncOrEmpty() } + + blockchainTogglesMap = fileBlockchainToggles + .mapValues { (blockchainId, isEnabled) -> + savedExcludedBlockchains[blockchainId] ?: isEnabled + } + .toMutableMap() + } + + override suspend fun excludeBlockchain(mainnetId: String, isExcluded: Boolean) { + blockchainTogglesMap[mainnetId] = isExcluded + + localTogglesStorage.store(blockchainTogglesMap) + } + + override fun isMatchLocalConfig(): Boolean { + return blockchainTogglesMap == fileBlockchainToggles + } + + override suspend fun recoverLocalConfig() { + blockchainTogglesMap = fileBlockchainToggles.toMutableMap() + + localTogglesStorage.store(blockchainTogglesMap) + } + + override fun toString(): String { + return blockchainTogglesMap + .filterKeys { it.isNotEmpty() } + .toTableString(tableName = this@DevExcludedBlockchainsManager::class.java.simpleName) + } + + private fun getFileBlockchainToggles(): Map { + val appVersion = versionProvider.get() + + return ExcludedBlockchainToggles.values.defineTogglesAvailability(appVersion = appVersion) + } +} \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/ProdExcludedBlockchainsManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/ProdExcludedBlockchainsManager.kt new file mode 100644 index 0000000000..bc9eb689c4 --- /dev/null +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/ProdExcludedBlockchainsManager.kt @@ -0,0 +1,27 @@ +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.utils.defineTogglesAvailability +import com.tangem.core.configtoggle.version.VersionProvider + +/** + * [ExcludedBlockchainsManager] implementation in PROD build + * + * @property versionProvider application version provider + */ +internal class ProdExcludedBlockchainsManager( + private val versionProvider: VersionProvider, +) : ExcludedBlockchainsManager { + + override val excludedBlockchainsIds: Set = getBlockchainToggles() + + private fun getBlockchainToggles(): Set { + val appVersion = versionProvider.get() + + return ExcludedBlockchainToggles.values + .defineTogglesAvailability(appVersion = appVersion) + .filterValues { !it } + .keys + } +} \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/ExcludedBlockchainsManagerModule.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/ExcludedBlockchainsManagerModule.kt index 2e4fcddac5..14d9514eae 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/ExcludedBlockchainsManagerModule.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/ExcludedBlockchainsManagerModule.kt @@ -3,11 +3,10 @@ package com.tangem.core.configtoggle.di import android.content.Context import com.tangem.core.configtoggle.BuildConfig import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager -import com.tangem.core.configtoggle.blockchain.MutableExcludedBlockchainsManager -import com.tangem.core.configtoggle.blockchain.impl.DefaultExcludedBlockchainsManager +import com.tangem.core.configtoggle.blockchain.impl.DevExcludedBlockchainsManager +import com.tangem.core.configtoggle.blockchain.impl.ProdExcludedBlockchainsManager import com.tangem.core.configtoggle.storage.LocalTogglesStorage import com.tangem.core.configtoggle.version.DefaultVersionProvider -import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.local.preferences.AppPreferencesStore import dagger.Module import dagger.Provides @@ -24,26 +23,20 @@ internal object ExcludedBlockchainsManagerModule { @Singleton fun provideExcludedBlockchainsManager( @ApplicationContext context: Context, - assetLoader: AssetLoader, appPreferencesStore: AppPreferencesStore, ): ExcludedBlockchainsManager { - val localTogglesStorage = LocalTogglesStorage(assetLoader) val versionProvider = DefaultVersionProvider(context) - return DefaultExcludedBlockchainsManager( - localTogglesStorage, - appPreferencesStore, - versionProvider, - ) - } - - @Provides - @Singleton - fun provideMutableExcludedBlockchainsManager( - manager: ExcludedBlockchainsManager, - ): MutableExcludedBlockchainsManager? { - if (!BuildConfig.TESTER_MENU_ENABLED) return null - - return manager as MutableExcludedBlockchainsManager + return if (BuildConfig.TESTER_MENU_ENABLED) { + DevExcludedBlockchainsManager( + versionProvider = versionProvider, + localTogglesStorage = LocalTogglesStorage( + appPreferencesStore = appPreferencesStore, + preferencesKey = LocalTogglesStorage.EXCLUDED_BLOCKCHAINS_KEY, + ), + ) + } else { + ProdExcludedBlockchainsManager(versionProvider = versionProvider) + } } } \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/FeatureTogglesManagerModule.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/FeatureTogglesManagerModule.kt index 79ad0221fc..877f25842d 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/FeatureTogglesManagerModule.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/di/FeatureTogglesManagerModule.kt @@ -5,7 +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.storage.FeatureTogglesLocalStorage +import com.tangem.core.configtoggle.storage.LocalTogglesStorage import com.tangem.core.configtoggle.version.DefaultVersionProvider import com.tangem.datasource.local.preferences.AppPreferencesStore import dagger.Module @@ -30,7 +30,10 @@ internal object FeatureTogglesManagerModule { return if (BuildConfig.TESTER_MENU_ENABLED) { DevFeatureTogglesManager( versionProvider = versionProvider, - featureTogglesLocalStorage = FeatureTogglesLocalStorage(appPreferencesStore), + featureTogglesLocalStorage = LocalTogglesStorage( + appPreferencesStore = appPreferencesStore, + preferencesKey = LocalTogglesStorage.FEATURE_TOGGLES_KEY, + ), ) } else { ProdFeatureTogglesManager(versionProvider = versionProvider) diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt index 6677cae6d0..84dd090158 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt @@ -3,11 +3,11 @@ 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.storage.FeatureTogglesLocalStorage +import com.tangem.core.configtoggle.storage.LocalTogglesStorage import com.tangem.core.configtoggle.utils.defineTogglesAvailability +import com.tangem.core.configtoggle.utils.toTableString import com.tangem.core.configtoggle.version.VersionProvider import kotlinx.coroutines.runBlocking -import java.util.Locale import kotlin.properties.Delegates /** @@ -18,7 +18,7 @@ import kotlin.properties.Delegates */ internal class DevFeatureTogglesManager( private val versionProvider: VersionProvider, - private val featureTogglesLocalStorage: FeatureTogglesLocalStorage, + private val featureTogglesLocalStorage: LocalTogglesStorage, ) : MutableFeatureTogglesManager { private var fileFeatureTogglesMap: Map = getFileFeatureToggles() @@ -52,16 +52,7 @@ internal class DevFeatureTogglesManager( } override fun toString(): String { - return buildString { - append("DevFeatureTogglesManager:\n") - append("|------------------------------------------|-----------|\n") - append(String.format(Locale.getDefault(), "| %-40s | %-9s |\n", "name", "isEnabled")) - append("|------------------------------------------|-----------|\n") - featureTogglesMap.entries.forEachIndexed { index, (name, isEnabled) -> - append(String.format(Locale.getDefault(), "| %-40s | %-9s |\n", name, isEnabled)) - } - append("|------------------------------------------|-----------|") - } + return featureTogglesMap.toTableString(tableName = this@DevFeatureTogglesManager::class.java.simpleName) } private fun getFileFeatureToggles(): Map { diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/FeatureTogglesLocalStorage.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/FeatureTogglesLocalStorage.kt deleted file mode 100644 index 7ab7d42891..0000000000 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/FeatureTogglesLocalStorage.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.core.configtoggle.storage - -import androidx.datastore.preferences.core.stringPreferencesKey -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.utils.getObjectMapSync -import com.tangem.datasource.local.preferences.utils.storeObjectMap - -/** - * Local storage for feature toggles - * - * @property appPreferencesStore app preferences store - * -[REDACTED_AUTHOR] - */ -internal class FeatureTogglesLocalStorage( - private val appPreferencesStore: AppPreferencesStore, -) { - - suspend fun getSyncOrEmpty(): Map { - return appPreferencesStore.getObjectMapSync(key = FEATURE_TOGGLES_KEY) - } - - suspend fun store(value: Map) { - appPreferencesStore.storeObjectMap(key = FEATURE_TOGGLES_KEY, value = value) - } - - private companion object { - - val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") } - } -} \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorage.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorage.kt index 59086d9658..fe575dc0ef 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorage.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorage.kt @@ -1,24 +1,34 @@ package com.tangem.core.configtoggle.storage -import com.tangem.datasource.asset.loader.AssetLoader -import kotlin.properties.Delegates +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.stringPreferencesKey +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.utils.getObjectMapSync +import com.tangem.datasource.local.preferences.utils.storeObjectMap /** - * Storage implementation for storing local feature toggles. - * Feature toggles are declared in file [LOCAL_CONFIG_PATH]. + * Local storage for toggles * - * @property assetLoader asset loader + * @property appPreferencesStore app preferences store + * @property preferencesKey preferences key * [REDACTED_AUTHOR] */ internal class LocalTogglesStorage( - private val assetLoader: AssetLoader, -) : TogglesStorage { + private val appPreferencesStore: AppPreferencesStore, + private val preferencesKey: Preferences.Key, +) { - override var toggles: List by Delegates.notNull() - private set + suspend fun getSyncOrEmpty(): Map { + return appPreferencesStore.getObjectMapSync(key = preferencesKey) + } - override suspend fun populate(path: String) { - toggles = assetLoader.loadList(path) + suspend fun store(value: Map) { + appPreferencesStore.storeObjectMap(key = preferencesKey, value = value) + } + + companion object { + val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") } + val EXCLUDED_BLOCKCHAINS_KEY by lazy { stringPreferencesKey(name = "excludedBlockchainsV2") } } } \ No newline at end of file diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/CollectionExt.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/CollectionExt.kt index d3e54bcac1..052ec8ab62 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/CollectionExt.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/CollectionExt.kt @@ -1,17 +1,7 @@ package com.tangem.core.configtoggle.utils -import com.tangem.core.configtoggle.storage.ConfigToggle import com.tangem.core.configtoggle.version.VersionAvailabilityContract -internal fun List.associateToggles(currentVersion: String): Map { - return associate { localToggle -> - Pair( - first = localToggle.name, - second = VersionAvailabilityContract(currentVersion, localToggle.version), - ) - } -} - internal fun Map.defineTogglesAvailability(appVersion: String?): Map { return if (appVersion == null) { mapValues { false } diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/StringLogExt.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/StringLogExt.kt new file mode 100644 index 0000000000..85293bf111 --- /dev/null +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/utils/StringLogExt.kt @@ -0,0 +1,16 @@ +package com.tangem.core.configtoggle.utils + +import java.util.Locale + +internal fun Map.toTableString(tableName: String): String { + return buildString { + append("$tableName:\n") + append("|------------------------------------------|-----------|\n") + append(String.format(Locale.getDefault(), "| %-40s | %-9s |\n", "name", "isEnabled")) + append("|------------------------------------------|-----------|\n") + entries.forEachIndexed { index, (name, isEnabled) -> + append(String.format(Locale.getDefault(), "| %-40s | %-9s |\n", name, isEnabled)) + } + append("|------------------------------------------|-----------|") + } +} \ No newline at end of file diff --git a/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManagerTest.kt b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManagerTest.kt new file mode 100644 index 0000000000..18eb584b52 --- /dev/null +++ b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManagerTest.kt @@ -0,0 +1,203 @@ +package com.tangem.core.configtoggle.blockchain.impl + +import com.google.common.truth.Truth +import com.tangem.core.configtoggle.ExcludedBlockchainToggles +import com.tangem.core.configtoggle.storage.LocalTogglesStorage +import com.tangem.core.configtoggle.version.VersionProvider +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.* + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class DevExcludedBlockchainsManagerTest { + + private val versionProvider = mockk() + private val localTogglesStorage = mockk(relaxUnitFun = 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) + } + + @AfterEach + fun tearDownEach() { + clearMocks(versionProvider, localTogglesStorage) + } + + @Test + fun `successfully initialize manager`() = runTest { + // Arrange + val appVersion = "1.0.0" + val savedToggles = mapOf("CHAIN_1" to false, "CHAIN_2" to true) + every { versionProvider.get() } returns appVersion + coEvery { localTogglesStorage.getSyncOrEmpty() } returns savedToggles + + // Act + val actual = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage).excludedBlockchainsIds + + // Assert + val expected = setOf("CHAIN_1") + Truth.assertThat(actual).containsExactlyElementsIn(expected) + + coVerifyOrder { + versionProvider.get() + localTogglesStorage.getSyncOrEmpty() + } + } + + @Test + fun `successfully initialize manager if versionProvider returns null`() = runTest { + // Arrange + every { versionProvider.get() } returns null + coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap() + + // Act + val actual = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage).excludedBlockchainsIds + + // Assert + Truth.assertThat(actual).containsExactly("CHAIN_1", "CHAIN_2") + + coVerifyOrder { + versionProvider.get() + localTogglesStorage.getSyncOrEmpty() + } + } + + @Test + fun `successfully initialize manager if storage returns empty map`() = runTest { + // Arrange + every { versionProvider.get() } returns "1.0.0" + coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap() + + // Act + val actual = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage).excludedBlockchainsIds + + // Assert + Truth.assertThat(actual).containsExactly("CHAIN_2") + + coVerifyOrder { + versionProvider.get() + localTogglesStorage.getSyncOrEmpty() + } + } + + @Test + fun `failure initialize manager if storage throws exception`() = runTest { + // Arrange + every { versionProvider.get() } returns "1.0.0" + val exception = Exception("Test exception") + coEvery { localTogglesStorage.getSyncOrEmpty() } throws exception + + // Act + val actual = runCatching { DevExcludedBlockchainsManager(versionProvider, localTogglesStorage) } + .exceptionOrNull()!! + + // Assert + Truth.assertThat(actual).isInstanceOf(exception::class.java) + Truth.assertThat(actual).hasMessageThat().isEqualTo(exception.message) + + coVerifyOrder { + versionProvider.get() + localTogglesStorage.getSyncOrEmpty() + } + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class ExcludeBlockchain { + + @Test + fun excludeBlockchain_changesStatusAndSaves() = runTest { + // Arrange + every { versionProvider.get() } returns "1.0.0" + coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap() + + val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage) + manager.excludeBlockchain("CHAIN_1", false) + + // Act + val actual = manager.excludedBlockchainsIds + + // Assert + Truth.assertThat(actual).contains("CHAIN_1") + coVerify { localTogglesStorage.store(match { it["CHAIN_1"] == false }) } + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class IsMatchLocalConfig { + + @Test + fun isMatchLocalConfig_returnsTrueIfMatchesFile() = runTest { + // Arrange + every { versionProvider.get() } returns "1.0.0" + coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap() + + val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage) + + // Act + val actual = manager.isMatchLocalConfig() + + // Assert + Truth.assertThat(actual).isTrue() + } + + @Test + fun isMatchLocalConfig_returnsFalseIfDiffersFromFile() = runTest { + // Arrange + every { versionProvider.get() } returns "1.0.0" + coEvery { localTogglesStorage.getSyncOrEmpty() } returns emptyMap() + + val manager = DevExcludedBlockchainsManager(versionProvider, localTogglesStorage) + manager.excludeBlockchain("CHAIN_1", false) + + // Act + val actual = manager.isMatchLocalConfig() + + // Assert + Truth.assertThat(actual).isFalse() + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class RecoverLocalConfig { + + @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() + + // Act + val actual = manager.excludedBlockchainsIds + + // Assert + Truth.assertThat(actual).containsExactly("CHAIN_1", "CHAIN_2") + + unmockkObject(ExcludedBlockchainToggles) + clearMocks(versionProvider, localTogglesStorage) + } + } +} \ No newline at end of file diff --git a/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/blockchain/impl/ProdExcludedBlockchainsManagerTest.kt b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/blockchain/impl/ProdExcludedBlockchainsManagerTest.kt new file mode 100644 index 0000000000..68fdf3c8b6 --- /dev/null +++ b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/blockchain/impl/ProdExcludedBlockchainsManagerTest.kt @@ -0,0 +1,98 @@ +package com.tangem.core.configtoggle.blockchain.impl + +import com.google.common.truth.Truth +import com.tangem.core.configtoggle.ExcludedBlockchainToggles +import com.tangem.core.configtoggle.version.VersionProvider +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.* + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class ProdExcludedBlockchainsManagerTest { + + private val versionProvider = mockk() + + @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) + } + + @AfterEach + fun tearDownEach() { + clearMocks(versionProvider) + } + + @Test + fun `successfully initialize excluded blockchains`() = runTest { + // Arrange + val appVersion = "1.0.0" + every { versionProvider.get() } returns appVersion + + // Act + val actual = ProdExcludedBlockchainsManager(versionProvider).excludedBlockchainsIds + + // Assert + val expected = setOf("CHAIN_2") + Truth.assertThat(actual).containsExactlyElementsIn(expected) + + coVerifyOrder { versionProvider.get() } + } + + @Test + fun `all blockchains excluded if versionProvider returns null`() = runTest { + // Arrange + every { versionProvider.get() } returns null + + // Act + val actual = ProdExcludedBlockchainsManager(versionProvider).excludedBlockchainsIds + + // Assert + val expected = setOf("CHAIN_1", "CHAIN_2") + Truth.assertThat(actual).containsExactlyElementsIn(expected) + + coVerifyOrder { versionProvider.get() } + } + + @Test + fun `all blockchains excluded if versionProvider returns empty string`() = runTest { + // Arrange + every { versionProvider.get() } returns "" + + // Act + val actual = ProdExcludedBlockchainsManager(versionProvider).excludedBlockchainsIds + + // Assert + val expected = setOf("CHAIN_1", "CHAIN_2") + Truth.assertThat(actual).containsExactlyElementsIn(expected) + + coVerifyOrder { versionProvider.get() } + } + + @Test + fun `failure initialize if versionProvider throws exception`() = runTest { + // Arrange + val exception = Exception("Test exception") + every { versionProvider.get() } throws exception + + // Act + val actual = runCatching { ProdExcludedBlockchainsManager(versionProvider) }.exceptionOrNull()!! + + // Assert + Truth.assertThat(actual).isInstanceOf(exception::class.java) + Truth.assertThat(actual).hasMessageThat().isEqualTo(exception.message) + + coVerifyOrder { versionProvider.get() } + } + } +} \ No newline at end of file diff --git a/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/manager/DevFeatureTogglesManagerTest.kt b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/manager/DevFeatureTogglesManagerTest.kt index e91cc5d705..4970a9c1cf 100644 --- a/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/manager/DevFeatureTogglesManagerTest.kt +++ b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/manager/DevFeatureTogglesManagerTest.kt @@ -5,7 +5,7 @@ import com.tangem.common.test.utils.ProvideTestModels 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.storage.FeatureTogglesLocalStorage +import com.tangem.core.configtoggle.storage.LocalTogglesStorage import com.tangem.core.configtoggle.version.VersionProvider import io.mockk.* import kotlinx.coroutines.test.runTest @@ -19,7 +19,7 @@ import org.junit.jupiter.params.ParameterizedTest internal class DevFeatureTogglesManagerTest { private val versionProvider = mockk() - private val featureTogglesLocalStorage = mockk(relaxUnitFun = true) + private val featureTogglesLocalStorage = mockk(relaxUnitFun = true) @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) diff --git a/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorageTest.kt b/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorageTest.kt deleted file mode 100644 index b2f5bc4be7..0000000000 --- a/core/config-toggles/src/test/kotlin/com/tangem/core/configtoggle/storage/LocalTogglesStorageTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.tangem.core.configtoggle.storage - -import android.annotation.SuppressLint -import com.google.common.truth.Truth -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.Types -import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants -import com.tangem.datasource.asset.loader.AssetLoader -import com.tangem.datasource.asset.reader.AssetReader -import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.* -import kotlinx.coroutines.test.runTest -import org.junit.Test -import java.io.IOException - -/** -[REDACTED_AUTHOR] - */ -@SuppressLint("CheckResult") -internal class LocalTogglesStorageTest { - - private val assetReader = mockk() - private val moshi = mockk() - private val jsonAdapter = mockk>>() - - // Impossible to mockk AssetLoader because it implement inline functions - private val assetLoader = AssetLoader( - assetReader = assetReader, - moshi = moshi, - dispatchers = TestingCoroutineDispatcherProvider(), - ) - - private val storage = LocalTogglesStorage(assetLoader) - - @Test - fun `successfully initialize storage`() = runTest { - everyReadingJson() returns json - everyCreatingMoshiAdapter() returns jsonAdapter - everyMappingJson() returns featureToggles - - storage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) - - coVerifyOrder { - assetReader.read(CONFIG_FILE_NAME) - jsonAdapter.fromJson(json) - } - - Truth.assertThat(storage.toggles).containsExactlyElementsIn(featureToggles) - } - - @Test - fun `failure initialize storage if assetReader throws exception`() = runTest { - everyReadingJson() returns json - everyCreatingMoshiAdapter() returns jsonAdapter - everyMappingJson() throws IOException() - - storage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) - - coVerifyOrder { - assetReader.read(CONFIG_FILE_NAME) - jsonAdapter.fromJson(json) - } - - Truth.assertThat(storage.toggles).containsExactlyElementsIn(emptyList()) - } - - @Test - fun `failure initialize storage if jsonAdapter throws exception`() = runTest { - everyReadingJson() throws IOException() - - storage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) - - coVerifyOrder { assetReader.read(CONFIG_FILE_NAME) } - verifyAll(inverse = true) { jsonAdapter.fromJson(any()) } - - Truth.assertThat(storage.toggles).containsExactlyElementsIn(emptyList()) - } - - private fun everyReadingJson() = coEvery { assetReader.read(CONFIG_FILE_NAME) } - - private fun everyCreatingMoshiAdapter() = every { - val types = Types.newParameterizedType(List::class.java, ConfigToggle::class.java) - moshi.adapter>(types) - } - - private fun everyMappingJson() = every { jsonAdapter.fromJson(json) } - - private companion object { - const val CONFIG_FILE_NAME = "configs/feature_toggles_config.json" - - val json = """ - [ - { - "name": "INACTIVE_TEST_FEATURE_ENABLED", - "version": "undefined" - }, - { - "name": "ACTIVE2_TEST_FEATURE_ENABLED", - "version": "1.0.0" - } - ] - """.trimIndent() - - val featureToggles = listOf( - ConfigToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"), - ConfigToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"), - ) - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 162d75a40f..8644fb72c8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -56,8 +56,6 @@ object PreferencesKeys { val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") } - val EXCLUDED_BLOCKCHAINS_KEY by lazy { stringPreferencesKey(name = "excludedBlockchainsV2") } - val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") } val IS_TANGEM_TOS_ACCEPTED_KEY by lazy { booleanPreferencesKey(name = "tangem_tos_accepted") } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/excludedblockchains/ExcludedBlockchainsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/excludedblockchains/ExcludedBlockchainsViewModel.kt index 213bfbc246..3c2015978a 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/excludedblockchains/ExcludedBlockchainsViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/excludedblockchains/ExcludedBlockchainsViewModel.kt @@ -3,6 +3,7 @@ package com.tangem.feature.tester.presentation.excludedblockchains import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.blockchain.common.Blockchain +import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager import com.tangem.core.configtoggle.blockchain.MutableExcludedBlockchainsManager import com.tangem.core.navigation.finisher.AppFinisher import com.tangem.core.ui.components.fields.entity.SearchBarUM @@ -25,11 +26,11 @@ import javax.inject.Inject @HiltViewModel internal class ExcludedBlockchainsViewModel @Inject constructor( private val appVersionProvider: AppVersionProvider, - excludedBlockchainsManager: MutableExcludedBlockchainsManager?, + excludedBlockchainsManager: ExcludedBlockchainsManager, ) : ViewModel() { private val excludedBlockchainsManager: MutableExcludedBlockchainsManager = - requireNotNull(excludedBlockchainsManager) { + requireNotNull(excludedBlockchainsManager as? MutableExcludedBlockchainsManager) { "Mutable excluded blockchains manager can't be null when tester actions is available" } diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/ExcludedBlockchains.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/ExcludedBlockchains.kt index 6e2cb4877d..e2d3d72ea2 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/ExcludedBlockchains.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/ExcludedBlockchains.kt @@ -28,10 +28,6 @@ class ExcludedBlockchains @Inject internal constructor( excludedBlockchainsManager = object : ExcludedBlockchainsManager { override val excludedBlockchainsIds: Set = emptySet() - - override suspend fun init() { - /* no-op */ - } }, )