Updated on 2026-08-14
This commit is contained in:
parent
dfc3dbc324
commit
513e0a4d7c
20 changed files with 460 additions and 294 deletions
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ android {
|
|||
}
|
||||
|
||||
tasks.named("preBuild") {
|
||||
dependsOn(generateFeatureToggles /*generateExcludedBlockchainToggles*/)
|
||||
dependsOn(generateFeatureToggles, generateExcludedBlockchainToggles)
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,4 @@ package com.tangem.core.configtoggle.blockchain
|
|||
interface ExcludedBlockchainsManager {
|
||||
|
||||
val excludedBlockchainsIds: Set<String>
|
||||
|
||||
suspend fun init()
|
||||
}
|
||||
|
|
@ -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<String, Boolean>
|
||||
private lateinit var localExcludedBlockchains: Map<String, Boolean>
|
||||
|
||||
override val excludedBlockchainsIds: Set<String>
|
||||
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<Boolean>(
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Boolean> = getFileBlockchainToggles()
|
||||
private var blockchainTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
|
||||
|
||||
override val excludedBlockchainsIds: Set<String>
|
||||
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<String, Boolean> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return ExcludedBlockchainToggles.values.defineTogglesAvailability(appVersion = appVersion)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> = getBlockchainToggles()
|
||||
|
||||
private fun getBlockchainToggles(): Set<String> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return ExcludedBlockchainToggles.values
|
||||
.defineTogglesAvailability(appVersion = appVersion)
|
||||
.filterValues { !it }
|
||||
.keys
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<String, Boolean> = 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<String, Boolean> {
|
||||
|
|
|
|||
|
|
@ -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<String, Boolean> {
|
||||
return appPreferencesStore.getObjectMapSync<Boolean>(key = FEATURE_TOGGLES_KEY)
|
||||
}
|
||||
|
||||
suspend fun store(value: Map<String, Boolean>) {
|
||||
appPreferencesStore.storeObjectMap(key = FEATURE_TOGGLES_KEY, value = value)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
) {
|
||||
|
||||
override var toggles: List<ConfigToggle> by Delegates.notNull()
|
||||
private set
|
||||
suspend fun getSyncOrEmpty(): Map<String, Boolean> {
|
||||
return appPreferencesStore.getObjectMapSync<Boolean>(key = preferencesKey)
|
||||
}
|
||||
|
||||
override suspend fun populate(path: String) {
|
||||
toggles = assetLoader.loadList<ConfigToggle>(path)
|
||||
suspend fun store(value: Map<String, Boolean>) {
|
||||
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") }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ConfigToggle>.associateToggles(currentVersion: String): Map<String, Boolean> {
|
||||
return associate { localToggle ->
|
||||
Pair(
|
||||
first = localToggle.name,
|
||||
second = VersionAvailabilityContract(currentVersion, localToggle.version),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Map<String, String>.defineTogglesAvailability(appVersion: String?): Map<String, Boolean> {
|
||||
return if (appVersion == null) {
|
||||
mapValues { false }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.core.configtoggle.utils
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
internal fun Map<String, Boolean>.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("|------------------------------------------|-----------|")
|
||||
}
|
||||
}
|
||||
|
|
@ -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<VersionProvider>()
|
||||
private val localTogglesStorage = mockk<LocalTogglesStorage>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<VersionProvider>()
|
||||
|
||||
@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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<VersionProvider>()
|
||||
private val featureTogglesLocalStorage = mockk<FeatureTogglesLocalStorage>(relaxUnitFun = true)
|
||||
private val featureTogglesLocalStorage = mockk<LocalTogglesStorage>(relaxUnitFun = true)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
|
|
|
|||
|
|
@ -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<AssetReader>()
|
||||
private val moshi = mockk<Moshi>()
|
||||
private val jsonAdapter = mockk<JsonAdapter<List<ConfigToggle>>>()
|
||||
|
||||
// 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<ConfigToggle>())
|
||||
}
|
||||
|
||||
@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<String>()) }
|
||||
|
||||
Truth.assertThat(storage.toggles).containsExactlyElementsIn(emptyList<ConfigToggle>())
|
||||
}
|
||||
|
||||
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<List<ConfigToggle>>(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"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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") }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,6 @@ class ExcludedBlockchains @Inject internal constructor(
|
|||
excludedBlockchainsManager = object : ExcludedBlockchainsManager {
|
||||
|
||||
override val excludedBlockchainsIds: Set<String> = emptySet()
|
||||
|
||||
override suspend fun init() {
|
||||
/* no-op */
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue