Updated on 2026-08-14
This commit is contained in:
commit
3fba2ae059
872 changed files with 17188 additions and 8108 deletions
|
|
@ -11,20 +11,36 @@
|
|||
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
|
||||
"version": "5.10.0"
|
||||
},
|
||||
{
|
||||
"name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED",
|
||||
"version": "5.8.0"
|
||||
},
|
||||
{
|
||||
"name": "LOCAL_USER_LOGS_ENABLED",
|
||||
"version": "5.12.0"
|
||||
"version": "5.13.0"
|
||||
},
|
||||
{
|
||||
"name": "GENERATE_XPUB_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.12.0"
|
||||
},
|
||||
{
|
||||
"name": "WC_SOLANA_TX_SIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TOKEN_LIST_LCE_ENABLED",
|
||||
"version": "5.12.0"
|
||||
},
|
||||
{
|
||||
"name": "CARDANO_TOKENS_SUPPORT_ENABLED",
|
||||
"version": "5.12.0"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "FULL_RESET_ENABLED",
|
||||
"version": "5.12.0"
|
||||
},
|
||||
{
|
||||
"name": "DETAILS_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
package com.tangem.core.featuretoggle.di
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.core.featuretoggle.manager.DevFeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.manager.ProdFeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage
|
||||
import com.tangem.core.featuretoggle.version.DefaultVersionProvider
|
||||
import com.tangem.core.featuretoggles.BuildConfig
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -25,17 +22,12 @@ internal object FeatureTogglesManagerModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun provideFeatureTogglesManager(
|
||||
@ApplicationContext context: Context,
|
||||
assetReader: AssetReader,
|
||||
assetLoader: AssetLoader,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): FeatureTogglesManager {
|
||||
val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
|
||||
val localFeatureTogglesStorage = LocalFeatureTogglesStorage(
|
||||
assetReader = assetReader,
|
||||
jsonAdapter = moshi.adapter(),
|
||||
)
|
||||
val localFeatureTogglesStorage = LocalFeatureTogglesStorage(assetLoader)
|
||||
val versionProvider = DefaultVersionProvider(context)
|
||||
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
|
|
|
|||
|
|
@ -1,41 +1,28 @@
|
|||
package com.tangem.core.featuretoggle.storage
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage.Companion.LOCAL_CONFIG_PATH
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import timber.log.Timber
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Storage implementation for storing local feature toggles.
|
||||
* Feature toggles are declared in file [LOCAL_CONFIG_PATH].
|
||||
*
|
||||
* @property assetReader asset reader
|
||||
* @property jsonAdapter adapter for parsing local json config
|
||||
* @property assetLoader asset loader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class LocalFeatureTogglesStorage(
|
||||
private val assetReader: AssetReader,
|
||||
private val jsonAdapter: JsonAdapter<List<FeatureToggle>>,
|
||||
private val assetLoader: AssetLoader,
|
||||
) : FeatureTogglesStorage {
|
||||
|
||||
override var featureToggles: List<FeatureToggle> by Delegates.notNull()
|
||||
private set
|
||||
|
||||
override suspend fun init() {
|
||||
runCatching { requireNotNull(jsonAdapter.fromJson(assetReader.readJson(LOCAL_CONFIG_PATH))) }
|
||||
.onSuccess { featureToggles = it }
|
||||
.onFailure { Timber.e(LocalFeatureTogglesStorage::class.java.name, "Failed to parse $LOCAL_CONFIG_PATH") }
|
||||
featureToggles = assetLoader.loadList<FeatureToggle>(LOCAL_CONFIG_PATH)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun getConfigPath() = LOCAL_CONFIG_PATH
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun getFeatureToggles(): Iterable<FeatureToggle> = featureToggles
|
||||
|
||||
private companion object {
|
||||
const val LOCAL_CONFIG_PATH: String = "configs/feature_toggles_config"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ package com.tangem.core.featuretoggle.storage
|
|||
import android.annotation.SuppressLint
|
||||
import com.google.common.truth.Truth
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verifyAll
|
||||
import io.mockk.verifyOrder
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
|
|
@ -16,75 +15,73 @@ import java.io.IOException
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SuppressLint("CheckResult")
|
||||
internal class LocalFeatureTogglesStorageTest {
|
||||
|
||||
private val assetReader = mockk<AssetReader>()
|
||||
private val moshi = mockk<Moshi>()
|
||||
private val jsonAdapter = mockk<JsonAdapter<List<FeatureToggle>>>()
|
||||
private val storage = LocalFeatureTogglesStorage(assetReader, jsonAdapter)
|
||||
|
||||
// Impossible to mockk AssetLoader because it implement inline functions
|
||||
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
|
||||
|
||||
private val storage = LocalFeatureTogglesStorage(assetLoader)
|
||||
|
||||
@Test
|
||||
fun `successfully initialize storage`() = runTest {
|
||||
coEvery { assetReader.readJson(storage.getConfigPath()) } returns json
|
||||
coEvery { jsonAdapter.fromJson(json) } returns featureToggles
|
||||
everyReadingJson() returns json
|
||||
everyCreatingMoshiAdapter() returns jsonAdapter
|
||||
everyMappingJson() returns featureToggles
|
||||
|
||||
storage.init()
|
||||
|
||||
verifyOrder {
|
||||
assetReader.readJson(storage.getConfigPath())
|
||||
coVerifyOrder {
|
||||
assetReader.read(CONFIG_FILE_NAME)
|
||||
jsonAdapter.fromJson(json)
|
||||
}
|
||||
|
||||
Truth.assertThat(storage.getFeatureToggles()).containsExactlyElementsIn(featureToggles)
|
||||
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(featureToggles)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failure initialize storage if assetReader throws exception`() = runTest {
|
||||
coEvery { assetReader.readJson(storage.getConfigPath()) } returns json
|
||||
coEvery { jsonAdapter.fromJson(json) } throws IOException()
|
||||
everyReadingJson() returns json
|
||||
everyCreatingMoshiAdapter() returns jsonAdapter
|
||||
everyMappingJson() throws IOException()
|
||||
|
||||
storage.init()
|
||||
|
||||
verifyOrder {
|
||||
assetReader.readJson(storage.getConfigPath())
|
||||
coVerifyOrder {
|
||||
assetReader.read(CONFIG_FILE_NAME)
|
||||
jsonAdapter.fromJson(json)
|
||||
}
|
||||
|
||||
runCatching { storage.getFeatureToggles() }
|
||||
.onSuccess { throw IllegalStateException("featureToggles shouldn't be initialized") }
|
||||
.onFailure {
|
||||
Truth
|
||||
.assertThat(it)
|
||||
.hasMessageThat()
|
||||
.contains("Property featureToggles should be initialized before get.")
|
||||
|
||||
Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java)
|
||||
}
|
||||
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(emptyList<FeatureToggle>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failure initialize storage if jsonAdapter throws exception`() = runTest {
|
||||
coEvery { assetReader.readJson(storage.getConfigPath()) } throws IOException()
|
||||
everyReadingJson() throws IOException()
|
||||
|
||||
storage.init()
|
||||
|
||||
verifyOrder { assetReader.readJson(storage.getConfigPath()) }
|
||||
coVerifyOrder { assetReader.read(CONFIG_FILE_NAME) }
|
||||
verifyAll(inverse = true) { jsonAdapter.fromJson(any<String>()) }
|
||||
|
||||
runCatching { storage.getFeatureToggles() }
|
||||
.onSuccess { throw IllegalStateException("featureToggles shouldn't be initialized") }
|
||||
.onFailure {
|
||||
Truth
|
||||
.assertThat(it)
|
||||
.hasMessageThat()
|
||||
.contains("Property featureToggles should be initialized before get.")
|
||||
|
||||
Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java)
|
||||
}
|
||||
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(emptyList<FeatureToggle>())
|
||||
}
|
||||
|
||||
private fun everyReadingJson() = coEvery { assetReader.read(CONFIG_FILE_NAME) }
|
||||
|
||||
private fun everyCreatingMoshiAdapter() = every {
|
||||
val types = Types.newParameterizedType(List::class.java, FeatureToggle::class.java)
|
||||
moshi.adapter<List<FeatureToggle>>(types)
|
||||
}
|
||||
|
||||
private fun everyMappingJson() = every { jsonAdapter.fromJson(json) }
|
||||
|
||||
private companion object {
|
||||
const val CONFIG_FILE_NAME = "configs/feature_toggles_config.json"
|
||||
|
||||
val json = """
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue