Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-05 15:42:16 +03:00
parent 820578708c
commit 83d291d7d0
8 changed files with 422 additions and 323 deletions

View file

@ -2,7 +2,8 @@ package com.tangem.blockchainsdk.providers
import com.google.common.truth.Truth
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.blockchainsdk.providers.BlockchainProvidersResponseMergerTest.Companion.localResponse
import com.tangem.blockchainsdk.providers.BlockchainProvidersResponseMergerTest.Companion.remoteResponse
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
import com.tangem.datasource.local.config.providers.models.ProviderModel
@ -17,229 +18,71 @@ import org.junit.Test
*/
internal class BlockchainProvidersResponseLoaderTest {
private val tangemTechServiceApi = mockk<TangemTechApi>()
private val tangemTechApi = mockk<TangemTechApi>()
private val blockchainProvidersStorage = mockk<BlockchainProvidersStorage>()
private val loader = BlockchainProvidersResponseLoader(
tangemTechServiceApi = tangemTechServiceApi,
tangemTechApi = tangemTechApi,
blockchainProvidersStorage = blockchainProvidersStorage,
dispatchers = TestingCoroutineDispatcherProvider(),
)
private val firebaseCrashlytics = mockk<FirebaseCrashlytics>()
@Before
fun setup() {
mockkStatic(FirebaseCrashlytics::class)
val firebaseCrashlytics = mockk<FirebaseCrashlytics>()
every { FirebaseCrashlytics.getInstance() } returns firebaseCrashlytics
every { firebaseCrashlytics.recordException(any()) } just Runs
}
@Test
fun test_load_if_local_config_is_empty() = runTest {
fun test_if_local_config_is_empty() = runTest {
coEvery { blockchainProvidersStorage.getConfigSync() } returns emptyMap()
val expected = null
val actual = loader.load()
coVerifyOrder { blockchainProvidersStorage.getConfigSync() }
coVerify(inverse = true) { tangemTechServiceApi.getBlockchainProviders() }
Truth.assertThat(actual).isEqualTo(null)
}
@Test
fun test_load_if_remote_config_loading_is_loaded_failure() = runTest {
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } throws IllegalStateException("Test exception")
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
Truth.assertThat(actual).isEqualTo(localProviders)
}
@Test
fun test_load_if_remote_config_loading_is_loaded_successful() = runTest {
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProviders
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
Truth.assertThat(actual).isEqualTo(remoteProviders)
}
@Test
fun test_load_if_remote_config_is_the_same_as_local() = runTest {
val remoteProviders = localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProviders
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
Truth.assertThat(actual).isEqualTo(remoteProviders)
}
@Test
fun test_load_if_remote_config_has_empty_providers() = runTest {
val ethProvider = "ethereum" to emptyList<ProviderModel>()
val remoteProvidersWithEth = remoteProviders + ethProvider
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithEth
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
Truth.assertThat(actual).isEqualTo(remoteProviders)
}
@Test
fun test_load_if_local_config_has_empty_providers() = runTest {
val localProvidersWithEmptyApt = localProviders.mapValues { if (it.key == "aptos") emptyList() else it.value }
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProvidersWithEmptyApt
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProviders
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
val expected = localProvidersWithEmptyApt + remoteProviders
coVerify(inverse = true) { tangemTechApi.getBlockchainProviders() }
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_load_if_remote_config_doesnt_contain_local_providers() = runTest {
val remoteProvidersWithoutLocal: BlockchainProvidersResponse = remoteProviders - localProviders.keys
fun test_if_remote_config_loading_is_failed() = runTest {
coEvery { blockchainProvidersStorage.getConfigSync() } returns localResponse
coEvery { tangemTechApi.getBlockchainProviders() } throws IllegalStateException("Test exception")
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithoutLocal
everyCrashlyticsRecording() just Runs
val expected = localResponse
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
tangemTechApi.getBlockchainProviders()
}
Truth.assertThat(actual).isEqualTo(remoteProviders)
}
@Test
fun test_load_if_remote_config_contains_unsupported_types() = runTest {
val ethProvider = "ethereum" to listOf(ProviderModel.UnsupportedType, ProviderModel.Private(name = "nownodes"))
val remoteProvidersWithEth = remoteProviders + ethProvider
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithEth
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
val expected = remoteProviders + ("ethereum" to listOf(ProviderModel.Private(name = "nownodes")))
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_load_if_remote_config_contains_invalid_public_providers() = runTest {
val localEthProvider = "ethereum" to listOf(ProviderModel.Private(name = "nownodes"))
val localProvidersWithEth = localProviders + localEthProvider
fun test_if_remote_config_is_loaded_successfully() = runTest {
val eth = "ethereum" to listOf(ProviderModel.Private(name = "nownodes"))
val remoteEthProvider = "ethereum" to listOf(ProviderModel.UnsupportedType, ProviderModel.Public("adbw2138"))
val remoteProvidersWithEth = remoteProviders + remoteEthProvider
coEvery { blockchainProvidersStorage.getConfigSync() } returns localResponse + eth
coEvery { tangemTechApi.getBlockchainProviders() } returns remoteResponse
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProvidersWithEth
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithEth
everyCrashlyticsRecording() just Runs
// Because configs are merged in BlockchainProvidersResponseMerger
val expected = remoteResponse + eth
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
tangemTechApi.getBlockchainProviders()
}
val expected = remoteProviders + localEthProvider
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_load_if_configs_contain_public_providers_without_slash_in_the_end() = runTest {
val localPublicProviderUrl = "https://qwe.com"
val localEthProvider = "ethereum" to listOf(ProviderModel.Public(url = localPublicProviderUrl))
val localProvidersWithEth = localProviders + localEthProvider
val remotePublicProviderUrl = "https://rty.com"
val remoteDogeProvider = "dogecoin" to listOf(ProviderModel.Public(url = remotePublicProviderUrl))
val remoteProvidersWithDoge = remoteProviders + remoteDogeProvider
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProvidersWithEth
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithDoge
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
val expected = remoteProviders +
localEthProvider.copy(second = listOf(ProviderModel.Public(url = "$localPublicProviderUrl/"))) +
remoteDogeProvider.copy(second = listOf(ProviderModel.Public(url = "$remotePublicProviderUrl/")))
Truth.assertThat(actual).isEqualTo(expected)
}
private fun everyCrashlyticsRecording() = every { firebaseCrashlytics.recordException(any()) }
private companion object {
val localProviders: BlockchainProvidersResponse = mapOf(
"aptos" to listOf(ProviderModel.Private(name = "nownodes")),
"algorand" to listOf(
ProviderModel.Private(name = "nownodes"),
ProviderModel.Public(url = "https://public_alg.com/"),
),
)
val remoteProviders: BlockchainProvidersResponse = localProviders + mapOf(
"bitcoin" to listOf(ProviderModel.Private(name = "blockchair")),
)
}
}

View file

@ -0,0 +1,205 @@
package com.tangem.blockchainsdk.providers
import com.google.common.truth.Truth
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.datasource.local.config.providers.models.ProviderModel
import io.mockk.*
import org.junit.Before
import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class BlockchainProvidersResponseMergerTest {
@Before
fun setup() {
mockkStatic(FirebaseCrashlytics::class)
val firebaseCrashlytics = mockk<FirebaseCrashlytics>()
every { FirebaseCrashlytics.getInstance() } returns firebaseCrashlytics
every { firebaseCrashlytics.recordException(any()) } just Runs
}
@Test
fun test_if_both_configs_are_empty() {
val expected = emptyMap<String, List<ProviderModel>>()
val actual = BlockchainProvidersResponseMerger.merge(
local = emptyMap(),
remote = emptyMap(),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_local_config_is_empty() {
val expected = remoteResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = emptyMap(),
remote = remoteResponse,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_remote_config_is_empty() {
val expected = localResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponse,
remote = emptyMap(),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_both_configs_are_not_empty() {
val expected = remoteResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponse,
remote = remoteResponse,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_configs_are_equal() {
val expected = remoteResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = remoteResponse,
remote = remoteResponse,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_local_config_has_blockchain_with_empty_providers() {
val eth = "ethereum" to emptyList<ProviderModel>()
val localResponseWithEth = localResponse + eth
val expected = localResponseWithEth + remoteResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponseWithEth,
remote = remoteResponse,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_remote_config_has_blockchain_with_empty_providers() {
val expected = remoteResponse
val eth = "ethereum" to emptyList<ProviderModel>()
val remoteWithEth = remoteResponse + eth
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponse,
remote = remoteWithEth,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_remote_config_doesnt_contain_local_providers() {
val remoteWithoutLocal = remoteResponse - localResponse.keys
/**
* The expected result does not contain local blockchains, since they can be disabled remotely.
* For the opposite case, see [test_if_both_configs_are_not_empty].
*/
val expected = remoteResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponse,
remote = remoteWithoutLocal,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_remote_config_contains_unsupported_providers() {
val nowNodesProvider = ProviderModel.Private(name = "nownodes")
val eth = "ethereum" to listOf(ProviderModel.UnsupportedType, nowNodesProvider)
val remoteWithEth = remoteResponse + eth
val expected = remoteResponse + ("ethereum" to listOf(nowNodesProvider))
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponse,
remote = remoteWithEth,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_remote_config_contains_invalid_public_providers() {
val eth = "ethereum" to listOf(ProviderModel.UnsupportedType, ProviderModel.Public("adbw2138"))
val remoteWithEth = remoteResponse + eth
val expected = remoteResponse
val actual = BlockchainProvidersResponseMerger.merge(
local = localResponse,
remote = remoteWithEth,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun test_if_configs_contain_public_providers_without_slash_in_the_end() {
val eth = "ethereum" to listOf(ProviderModel.Public("https://qwe.com"))
val kaspa = "kaspa" to listOf(ProviderModel.Public("https://qwe.com"))
val localWithKaspa = localResponse + kaspa
val remoteWithEth = remoteResponse + eth
val expected = remoteResponse + eth.addSlash() + kaspa.addSlash()
val actual = BlockchainProvidersResponseMerger.merge(
local = localWithKaspa,
remote = remoteWithEth,
)
Truth.assertThat(actual).isEqualTo(expected)
}
companion object {
val localResponse: BlockchainProvidersResponse = mapOf(
"aptos" to listOf(ProviderModel.Private(name = "nownodes")),
"algorand" to listOf(
ProviderModel.Private(name = "nownodes"),
ProviderModel.Public(url = "https://public_alg.com/"),
),
)
// local + bitcoin
val remoteResponse: BlockchainProvidersResponse = mapOf(
"aptos" to listOf(ProviderModel.Private(name = "nownodes")),
"algorand" to listOf(
ProviderModel.Private(name = "nownodes"),
ProviderModel.Public(url = "https://public_alg.com/"),
),
"bitcoin" to listOf(ProviderModel.Private(name = "blockchair")),
)
private fun Pair<String, List<ProviderModel.Public>>.addSlash(): Pair<String, List<ProviderModel.Public>> {
return first to second.map { it.copy(url = "${it.url}/") }
}
}
}