From 750a696a8433c26bce901f80dfa72e8530f7e157 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 21 May 2025 16:23:13 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangemTech/models/UserTokensResponse.kt | 2 +- data/common/build.gradle.kts | 5 + .../UserTokensResponseAddressesEnricher.kt | 64 +++++ .../data/common/currency/UserTokensSaver.kt | 63 +++++ .../tangem/data/common/di/DataCommonModule.kt | 40 ++++ .../UserTokensBackwardCompatibility.kt | 4 +- ...UserTokensResponseAddressesEnricherTest.kt | 219 ++++++++++++++++++ .../common/currency/UserTokensSaverTest.kt | 175 ++++++++++++++ .../DefaultCustomTokensRepository.kt | 26 +-- .../managetokens/di/ManageTokensDataModule.kt | 15 +- .../tangem/data/tokens/di/TokensDataModule.kt | 3 + .../repository/DefaultCurrenciesRepository.kt | 57 ++--- .../data/tokens/utils/CustomTokensMerger.kt | 10 +- .../data/wallets/DefaultWalletsRepository.kt | 47 +--- .../wallets/DefaultWalletsRepositoryTest.kt | 99 ++------ .../wallets/repository/WalletsRepository.kt | 4 +- .../GetIsNotificationsEnabledUseCase.kt | 3 +- 17 files changed, 636 insertions(+), 200 deletions(-) create mode 100644 data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt create mode 100644 data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt rename data/{tokens/src/main/kotlin/com/tangem/data/tokens/utils => common/src/main/kotlin/com/tangem/data/common/tokens}/UserTokensBackwardCompatibility.kt (93%) create mode 100644 data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricherTest.kt create mode 100644 data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt index ca02c9f581..2476bf0a31 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt @@ -22,7 +22,7 @@ data class UserTokensResponse( @Json(name = "symbol") val symbol: String, @Json(name = "decimals") val decimals: Int, @Json(name = "contractAddress") val contractAddress: String?, - @Json(name = "addresses") val list: List? = null, + @Json(name = "addresses") val addresses: List? = null, ) { override fun equals(other: Any?): Boolean { val otherToken = other as? Token ?: return false diff --git a/data/common/build.gradle.kts b/data/common/build.gradle.kts index 7d10aec65e..bf01e8fb80 100644 --- a/data/common/build.gradle.kts +++ b/data/common/build.gradle.kts @@ -16,6 +16,7 @@ tasks.withType().configureEach { dependencies { /* Core */ implementation(projects.core.datasource) + implementation(projects.core.utils) /* Domain */ implementation(projects.domain.demo) @@ -23,6 +24,9 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.notifications.toggles) + implementation(projects.domain.networks) + implementation(projects.domain.wallets) /* Libs - SDK */ implementation(tangemDeps.blockchain) @@ -47,4 +51,5 @@ dependencies { testRuntimeOnly(deps.test.junit5.engine) testImplementation(deps.test.mockk) testImplementation(deps.test.truth) + testImplementation(deps.moshi) } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt new file mode 100644 index 0000000000..576278ad91 --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt @@ -0,0 +1,64 @@ +package com.tangem.data.common.currency + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.networks.multi.MultiNetworkStatusProducer +import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class UserTokensResponseAddressesEnricher @Inject constructor( + private val notificationsFeatureToggles: NotificationsFeatureToggles, + private val walletsRepository: WalletsRepository, + private val dispatchers: CoroutineDispatcherProvider, + private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse { + if (!notificationsFeatureToggles.isNotificationsEnabled) { + return response + } + + val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId) + + return withContext(dispatchers.default) { + val networksStatuses = if (isNotificationsEnabled) { + multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() + } else { + emptySet() + } + + val enrichedTokens = response.tokens.map { token -> + if (isNotificationsEnabled) { + val matchingNetwork = networksStatuses.find { status -> + status.network.backendId == token.networkId && + status.network.derivationPath.value == token.derivationPath + } ?: return@map token + + val networkAddress = when (matchingNetwork.value) { + is NetworkStatus.Verified -> (matchingNetwork.value as NetworkStatus.Verified).address + is NetworkStatus.NoAccount -> (matchingNetwork.value as NetworkStatus.NoAccount).address + else -> null + } + + val addresses = networkAddress + ?.availableAddresses + ?.map { it.value } + ?.toList() + .orEmpty() + + token.copy(addresses = addresses) + } else { + token.copy(addresses = emptyList()) + } + } + + response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled) + } + } +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt new file mode 100644 index 0000000000..a0907e1111 --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt @@ -0,0 +1,63 @@ +package com.tangem.data.common.currency + +import com.tangem.data.common.api.safeApiCall +import com.tangem.data.common.tokens.UserTokensBackwardCompatibility +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.storeObject +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import timber.log.Timber + +class UserTokensSaver constructor( + private val tangemTechApi: TangemTechApi, + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, + private val userTokensResponseAddressesEnricher: UserTokensResponseAddressesEnricher, +) { + private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() + + suspend fun store(userWalletId: UserWalletId, response: UserTokensResponse, useEnricher: Boolean = true) = + withContext(dispatchers.io) { + val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response) + val enrichedUserTokensResponse = if (useEnricher) { + userTokensResponseAddressesEnricher( + userWalletId = userWalletId, + response = compatibleUserTokensResponse, + ) + } else { + compatibleUserTokensResponse + } + appPreferencesStore.storeObject( + key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue), + value = enrichedUserTokensResponse, + ) + } + + suspend fun storeAndPush(userWalletId: UserWalletId, response: UserTokensResponse) { + val enrichedUserTokensResponse = userTokensResponseAddressesEnricher( + userWalletId = userWalletId, + response = response, + ) + store(userWalletId, enrichedUserTokensResponse, false) + push(userWalletId, enrichedUserTokensResponse, false) + } + + suspend fun push(userWalletId: UserWalletId, response: UserTokensResponse, useEnricher: Boolean = true) = + withContext(dispatchers.io) { + val enrichedUserTokensResponse = if (useEnricher) { + userTokensResponseAddressesEnricher( + userWalletId = userWalletId, + response = response, + ) + } else { + response + } + safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedUserTokensResponse).bind() }) { + Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}") + } + } +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt index c9f546e6dd..f443f4bd8a 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt @@ -3,9 +3,17 @@ package com.tangem.data.common.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.DefaultCardCryptoCurrencyFactory +import com.tangem.data.common.currency.UserTokensResponseAddressesEnricher +import com.tangem.data.common.currency.UserTokensSaver +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles +import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -30,4 +38,36 @@ internal object DataCommonModule { userTokensResponseStore = userTokensResponseStore, ) } + + @Provides + @Singleton + fun provideUserTokensEncricher( + notificationsFeatureToggles: NotificationsFeatureToggles, + walletsRepository: WalletsRepository, + multiNetworkStatusSupplier: MultiNetworkStatusSupplier, + dispatchers: CoroutineDispatcherProvider, + ): UserTokensResponseAddressesEnricher { + return UserTokensResponseAddressesEnricher( + notificationsFeatureToggles = notificationsFeatureToggles, + walletsRepository = walletsRepository, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + dispatchers = dispatchers, + ) + } + + @Provides + @Singleton + fun provideUserTokensSaver( + tangemTechApi: TangemTechApi, + appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, + enricher: UserTokensResponseAddressesEnricher, + ): UserTokensSaver { + return UserTokensSaver( + tangemTechApi = tangemTechApi, + appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, + userTokensResponseAddressesEnricher = enricher, + ) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt b/data/common/src/main/kotlin/com/tangem/data/common/tokens/UserTokensBackwardCompatibility.kt similarity index 93% rename from data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt rename to data/common/src/main/kotlin/com/tangem/data/common/tokens/UserTokensBackwardCompatibility.kt index a4b3f4116d..54e69b23a1 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/tokens/UserTokensBackwardCompatibility.kt @@ -1,4 +1,4 @@ -package com.tangem.data.tokens.utils +package com.tangem.data.common.tokens import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId @@ -9,7 +9,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse * Helper to apply compatibility changes for [UserTokensResponse] to support old saved tokens * in new application with new IDs */ -class UserTokensBackwardCompatibility { +internal class UserTokensBackwardCompatibility { fun applyCompatibilityAndGetUpdated(userTokensResponse: UserTokensResponse): UserTokensResponse { return userTokensResponse.copy( diff --git a/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricherTest.kt b/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricherTest.kt new file mode 100644 index 0000000000..7b96f75000 --- /dev/null +++ b/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricherTest.kt @@ -0,0 +1,219 @@ +package com.tangem.data.common.currency + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import com.google.common.truth.Truth.assertThat +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import com.tangem.domain.models.StatusSource +import com.tangem.domain.tokens.model.NetworkAddress +import io.mockk.clearAllMocks +import org.junit.After +import org.junit.Before +import org.junit.Test + +class UserTokensResponseAddressesEnricherTest { + + private lateinit var notificationsFeatureToggles: NotificationsFeatureToggles + private lateinit var walletsRepository: WalletsRepository + private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider() + private lateinit var multiNetworkStatusSupplier: MultiNetworkStatusSupplier + private lateinit var enricher: UserTokensResponseAddressesEnricher + + @Before + fun setup() { + notificationsFeatureToggles = mockk() + walletsRepository = mockk() + multiNetworkStatusSupplier = mockk() + + enricher = UserTokensResponseAddressesEnricher( + notificationsFeatureToggles = notificationsFeatureToggles, + walletsRepository = walletsRepository, + dispatchers = dispatchers, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + ) + } + + @After + fun tearDown() { + clearAllMocks() + } + + @Test + fun `GIVEN notifications are disabled globally WHEN invoke THEN return original response`() = runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val token = createToken() + val response = createUserTokensResponse(tokens = listOf(token)) + every { notificationsFeatureToggles.isNotificationsEnabled } returns false + + // WHEN + val result = enricher(userWalletId, response) + + // THEN + assertThat(result).isEqualTo(response) + } + + @Test + fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() = + runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val token = createToken() + val response = createUserTokensResponse(tokens = listOf(token)) + every { notificationsFeatureToggles.isNotificationsEnabled } returns true + coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns false + coEvery { + multiNetworkStatusSupplier.invoke(any()) + } returns flowOf( + setOf( + NetworkStatus( + network = mockk { + every { backendId } returns "ethereum" + every { derivationPath.value } returns "m/44'/60'/0'/0/0" + }, + value = NetworkStatus.Verified( + address = mockk { + every { availableAddresses } returns emptySet() + }, + amounts = emptyMap(), + pendingTransactions = emptyMap(), + source = StatusSource.ACTUAL, + ), + ), + ), + ) + + // WHEN + val result = enricher(userWalletId, response) + + // THEN + assertThat(result.tokens).hasSize(1) + assertThat(result.tokens[0].addresses).isEmpty() + } + + @Test + fun `GIVEN notifications are enabled and addresses available WHEN invoke THEN return enriched response`() = + runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val token = createToken() + val response = createUserTokensResponse(tokens = listOf(token)) + val addresses = listOf("0x123", "0x456") + + every { notificationsFeatureToggles.isNotificationsEnabled } returns true + coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true + coEvery { + multiNetworkStatusSupplier.invoke(any()) + } returns flowOf( + setOf( + NetworkStatus( + network = mockk { + every { backendId } returns "ethereum" + every { derivationPath.value } returns "m/44'/60'/0'/0/0" + }, + value = NetworkStatus.Verified( + address = mockk { + every { availableAddresses } returns addresses.map { address -> + mockk { + every { value } returns address + } + }.toSet() + }, + amounts = emptyMap(), + pendingTransactions = emptyMap(), + source = StatusSource.ACTUAL, + ), + ), + ), + ) + + // WHEN + val result = enricher(userWalletId, response) + + // THEN + assertThat(result.tokens).hasSize(1) + assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses) + } + + @Test + fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val token = createToken() + val response = createUserTokensResponse(tokens = listOf(token)) + + every { notificationsFeatureToggles.isNotificationsEnabled } returns true + coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true + coEvery { + multiNetworkStatusSupplier.invoke(any()) + } returns flowOf( + setOf( + NetworkStatus( + network = mockk { + every { backendId } returns "bitcoin" + every { derivationPath.value } returns "m/44'/0'/0'/0/0" + }, + value = NetworkStatus.Verified( + address = mockk { + every { availableAddresses } returns emptySet() + }, + amounts = emptyMap(), + pendingTransactions = emptyMap(), + source = StatusSource.ACTUAL, + ), + ), + ), + ) + + // WHEN + val result = enricher(userWalletId, response) + + // THEN + assertThat(result.tokens).hasSize(1) + assertThat(result.tokens[0]).isEqualTo(token) + } + + private fun createToken( + networkId: String = "ethereum", + derivationPath: String = "m/44'/60'/0'/0/0", + list: List = emptyList(), + name: String = "Ethereum", + symbol: String = "ETH", + decimals: Int = 18, + contractAddress: String? = null, + id: String? = null, + ) = UserTokensResponse.Token( + id = id, + networkId = networkId, + derivationPath = derivationPath, + name = name, + symbol = symbol, + decimals = decimals, + contractAddress = contractAddress, + addresses = list, + ) + + private fun createUserTokensResponse( + tokens: List = emptyList(), + version: Int = 0, + group: UserTokensResponse.GroupType = UserTokensResponse.GroupType.NETWORK, + sort: UserTokensResponse.SortType = UserTokensResponse.SortType.BALANCE, + notifyStatus: Boolean? = null, + ) = UserTokensResponse( + version = version, + group = group, + sort = sort, + notifyStatus = notifyStatus, + tokens = tokens, + ) +} \ No newline at end of file diff --git a/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt b/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt new file mode 100644 index 0000000000..f72c44f771 --- /dev/null +++ b/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt @@ -0,0 +1,175 @@ +package com.tangem.data.common.currency + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import com.squareup.moshi.Moshi +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.* +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test + +class UserTokensSaverTest { + + private val tangemTechApi: TangemTechApi = mockk() + private val dataStore: DataStore = mockk(relaxed = true) + private val appPreferenceStore = AppPreferencesStore( + moshi = Moshi.Builder().build(), + dispatchers = TestingCoroutineDispatcherProvider(), + preferencesDataStore = dataStore, + ) + private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider() + private val userTokensResponseAddressesEnricher: UserTokensResponseAddressesEnricher = mockk() + + private lateinit var userTokensSaver: UserTokensSaver + + @Before + fun setup() { + coEvery { dataStore.data } returns flowOf(mockk(relaxed = true)) + userTokensSaver = UserTokensSaver( + tangemTechApi = tangemTechApi, + appPreferencesStore = appPreferenceStore, + dispatchers = dispatchers, + userTokensResponseAddressesEnricher = userTokensResponseAddressesEnricher, + ) + } + + @After + fun tearDown() { + clearAllMocks() + } + + @Test + fun `GIVEN user wallet id and response WHEN store THEN should store enriched response`() = runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val response = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + val enrichedResponse = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse + coEvery { dataStore.updateData(any()) } returns mockk(relaxed = true) + + // WHEN + userTokensSaver.store(userWalletId, response) + + // THEN + coVerify { + dataStore.updateData(any()) + } + coVerify(exactly = 0) { + tangemTechApi.saveUserTokens(any(), any()) + } + } + + @Test + fun `GIVEN user wallet id and response WHEN storeAndPush THEN should store and push enriched response`() = runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val response = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + val enrichedResponse = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse + coEvery { dataStore.updateData(any()) } returns mockk(relaxed = true) + coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns ApiResponse.Success(Unit) + + // WHEN + userTokensSaver.storeAndPush(userWalletId, response) + + // THEN + coVerify { + dataStore.updateData(any()) + tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse) + } + } + + @Test + fun `GIVEN user wallet id and response WHEN push THEN should push enriched response`() = runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val response = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + val enrichedResponse = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse + coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns ApiResponse.Success(Unit) + + // WHEN + userTokensSaver.push(userWalletId, response) + + // THEN + coVerify { + tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse) + } + coVerify(exactly = 0) { + dataStore.updateData(any()) + } + } + + @Test + fun `GIVEN user wallet id and response WHEN push AND api call fails THEN should log error`() = runTest { + // GIVEN + val userWalletId = UserWalletId("1234567890abcdef") + val response = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + val enrichedResponse = UserTokensResponse( + version = 0, + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + val error = ApiResponseError.UnknownException(Exception("API Error")) + coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse + coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns + ApiResponse.Error(error) as ApiResponse + + // WHEN + userTokensSaver.push(userWalletId, response) + + // THEN + coVerify { + tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse) + } + coVerify(exactly = 0) { + dataStore.updateData(any()) + } + } +} \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt index 18cf1f3b5d..f71d95b479 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt @@ -4,19 +4,17 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.getNetwork import com.tangem.data.managetokens.utils.TokenAddressesConverter -import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull -import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.extensions.canHandleBlockchain import com.tangem.domain.common.extensions.supportedBlockchains @@ -30,8 +28,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext -import timber.log.Timber +@Suppress("LongParameterList") internal class DefaultCustomTokensRepository( private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, @@ -39,12 +37,12 @@ internal class DefaultCustomTokensRepository( private val walletManagersFacade: WalletManagersFacade, private val excludedBlockchains: ExcludedBlockchains, private val dispatchers: CoroutineDispatcherProvider, + private val userTokensSaver: UserTokensSaver, ) : CustomTokensRepository { private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() private val tokenAddressConverter = TokenAddressesConverter() - private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean = withContext(dispatchers.io) { @@ -223,7 +221,7 @@ internal class DefaultCustomTokensRepository( } val token = userTokensResponseFactory.createResponseToken(cryptoCurrency) - storeAndPushTokens( + userTokensSaver.storeAndPush( userWalletId = userWalletId, response = storedCurrencies.copy(tokens = storedCurrencies.tokens.filterNot { it == token }), ) @@ -267,20 +265,4 @@ internal class DefaultCustomTokensRepository( value = sdkPath.rawPath, ) } - - private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { - val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response) - appPreferencesStore.storeObject( - key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue), - value = compatibleUserTokensResponse, - ) - - pushTokens(userWalletId, response) - } - - private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { - safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) { - Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}") - } - } } \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 73044971f5..7f82b1942a 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.managetokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.currency.CardCryptoCurrencyFactory +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.managetokens.DefaultCustomTokensRepository import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher @@ -56,14 +57,16 @@ internal object ManageTokensDataModule { walletManagersFacade: WalletManagersFacade, dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, + userTokensSaver: UserTokensSaver, ): CustomTokensRepository { return DefaultCustomTokensRepository( - tangemTechApi, - userWalletsStore, - appPreferencesStore, - walletManagersFacade, - excludedBlockchains, - dispatchers, + tangemTechApi = tangemTechApi, + userWalletsStore = userWalletsStore, + appPreferencesStore = appPreferencesStore, + walletManagersFacade = walletManagersFacade, + excludedBlockchains = excludedBlockchains, + dispatchers = dispatchers, + userTokensSaver = userTokensSaver, ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 3f3df8bc0f..7abad62119 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -3,6 +3,7 @@ package com.tangem.data.tokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.currency.CardCryptoCurrencyFactory +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.tokens.repository.DefaultCurrenciesRepository import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository @@ -40,6 +41,7 @@ internal object TokensDataModule { expressServiceLoader: ExpressServiceLoader, excludedBlockchains: ExcludedBlockchains, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, + tokensSaver: UserTokensSaver, ): CurrenciesRepository { return DefaultCurrenciesRepository( tangemTechApi = tangemTechApi, @@ -51,6 +53,7 @@ internal object TokensDataModule { dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + userTokensSaver = tokensSaver, ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 21c0c48815..84a9cf0999 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -7,7 +7,6 @@ import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.currency.* import com.tangem.data.tokens.utils.CustomTokensMerger -import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE @@ -19,7 +18,6 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObject import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull -import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.extensions.canHandleBlockchain import com.tangem.domain.common.util.cardTypesResolver @@ -51,14 +49,18 @@ internal class DefaultCurrenciesRepository( private val dispatchers: CoroutineDispatcherProvider, private val excludedBlockchains: ExcludedBlockchains, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, + private val userTokensSaver: UserTokensSaver, ) : CurrenciesRepository { private val demoConfig = DemoConfig() private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() - private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() - private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers) + private val customTokensMerger = CustomTokensMerger( + tangemTechApi = tangemTechApi, + dispatchers = dispatchers, + userTokensSaver = userTokensSaver, + ) override suspend fun saveTokens( userWalletId: UserWalletId, @@ -73,8 +75,7 @@ internal class DefaultCurrenciesRepository( isGroupedByNetwork = isGroupedByNetwork, isSortedByBalance = isSortedByBalance, ) - - storeAndPushTokens(userWalletId, response) + userTokensSaver.storeAndPush(userWalletId, response) } override suspend fun saveNewCurrenciesList(userWalletId: UserWalletId, currencies: List) { @@ -89,7 +90,7 @@ internal class DefaultCurrenciesRepository( val updatedResponse = savedResponse.copy( tokens = newCurrencies.map(userTokensResponseFactory::createResponseToken), ) - storeAndPushTokens( + userTokensSaver.storeAndPush( userWalletId = userWalletId, response = updatedResponse, ) @@ -113,7 +114,7 @@ internal class DefaultCurrenciesRepository( val updatedResponse = savedCurrencies.copy( tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken), ) - storeAndPushTokens( + userTokensSaver.storeAndPush( userWalletId = userWalletId, response = updatedResponse, ) @@ -177,9 +178,10 @@ internal class DefaultCurrenciesRepository( ) val token = userTokensResponseFactory.createResponseToken(currency) - storeAndPushTokens( + val updatedResponse = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token }) + userTokensSaver.storeAndPush( userWalletId = userWalletId, - response = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token }), + response = updatedResponse, ) } @@ -191,11 +193,12 @@ internal class DefaultCurrenciesRepository( ) val tokens = currencies.map(userTokensResponseFactory::createResponseToken) - storeAndPushTokens( + val updatedResponse = savedCurrencies.copy( + tokens = savedCurrencies.tokens.filterNot(tokens::contains), + ) + userTokensSaver.storeAndPush( userWalletId = userWalletId, - response = savedCurrencies.copy( - tokens = savedCurrencies.tokens.filterNot(tokens::contains), - ), + response = updatedResponse, ) } } @@ -583,13 +586,9 @@ internal class DefaultCurrenciesRepository( val compatibleUserTokensResponse = response .let { it.copy(tokens = it.tokens.distinct()) } - .let { customTokensMerger.mergeIfPresented(userWalletId, response) } - .let(userTokensBackwardCompatibility::applyCompatibilityAndGetUpdated) + .let { customTokensMerger.mergeIfPresented(userWalletId, it) } - appPreferencesStore.storeObject( - key = PreferencesKeys.getUserTokensKey(userWalletId = userWallet.walletId.stringValue), - value = compatibleUserTokensResponse, - ) + userTokensSaver.store(userWalletId, compatibleUserTokensResponse) fetchExpressAssetsByNetworkIds(userWalletId, compatibleUserTokensResponse) } @@ -600,16 +599,6 @@ internal class DefaultCurrenciesRepository( return demoConfig.isDemoCardId(userWallet.cardId) && response == null } - private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { - val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response) - appPreferencesStore.storeObject( - key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue), - value = compatibleUserTokensResponse, - ) - - pushTokens(userWalletId, response) - } - private suspend fun fetchExpressAssetsByNetworkIds(userWalletId: UserWalletId, userTokens: UserTokensResponse) { val tokens = userTokens.tokens.map { token -> LeastTokenInfo( @@ -657,7 +646,7 @@ internal class DefaultCurrenciesRepository( if (e is ApiResponseError.HttpException && e.code == ApiResponseError.HttpException.Code.NOT_FOUND) { Timber.w(e, "Requested currencies could not be found in the remote store for: $userWalletId") - pushTokens(userWalletId, response) + userTokensSaver.push(userWalletId, response) } else { cacheRegistry.invalidate(getTokensCacheKey(userWalletId)) } @@ -665,12 +654,6 @@ internal class DefaultCurrenciesRepository( return response } - private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { - safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) { - Timber.e(it, "Unable to save user tokens for: ${userWalletId.stringValue}") - } - } - private fun createDefaultUserTokensResponse(userWallet: UserWallet) = userTokensResponseFactory.createUserTokensResponse( currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CustomTokensMerger.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CustomTokensMerger.kt index da22162e0a..18895b5065 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CustomTokensMerger.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CustomTokensMerger.kt @@ -2,6 +2,7 @@ package com.tangem.data.tokens.utils import arrow.atomic.AtomicBoolean import com.tangem.data.common.api.safeApiCall +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.wallets.models.UserWalletId @@ -18,6 +19,7 @@ import timber.log.Timber internal class CustomTokensMerger( private val tangemTechApi: TangemTechApi, private val dispatchers: CoroutineDispatcherProvider, + private val userTokensSaver: UserTokensSaver, ) { /** @@ -47,7 +49,7 @@ internal class CustomTokensMerger( // previously here was used compare response.tokens, but it's not working correctly // because Token.equals() skip some fields if (wasMerged.value) { - pushTokens(userWalletId, updatedResponse) + userTokensSaver.push(userWalletId, updatedResponse) } return updatedResponse @@ -102,10 +104,4 @@ internal class CustomTokensMerger( }, ) } - - private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) { - safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) { - Timber.e(it, "Unable to save user tokens for: $userWalletId") - } - } } \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index 1b8c62c423..3c4e5fa0ac 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -238,26 +238,18 @@ internal class DefaultWalletsRepository( } } - override suspend fun isNotificationsEnabled(userWalletId: UserWalletId, force: Boolean): Boolean = - withContext(dispatchers.io) { - if (force) { - return@withContext loadAndSaveNotificationsEnabled(userWalletId) - } - - val localValue = appPreferencesStore.getObjectMap(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) - .map { it[userWalletId.stringValue] } - .firstOrNull() - - localValue ?: loadAndSaveNotificationsEnabled(userWalletId) - } + override suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean = + appPreferencesStore.getObjectMap(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) + .map { it[userWalletId.stringValue] } + .firstOrNull() ?: false override suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) { - withContext(dispatchers.io) { - tangemTechApi.setNotificationsEnabled( - walletId = userWalletId.stringValue, - body = WalletBody(notifyStatus = isEnabled), - ).getOrThrow() - setNotificationsEnabledLocally(userWalletId, isEnabled) + appPreferencesStore.editData { + it.setObjectMap( + key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY, + value = it.getObjectMap(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) + .plus(userWalletId.stringValue to isEnabled), + ) } } @@ -283,7 +275,7 @@ internal class DefaultWalletsRepository( value = walletInfo, ) if (updateCache) { - setNotificationsEnabledLocally( + setNotificationsEnabled( userWalletId = userWallet.walletId, isEnabled = userWallet.isNotificationsEnabled, ) @@ -307,21 +299,4 @@ internal class DefaultWalletsRepository( body = walletsBody, ).getOrThrow() } - - private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean { - val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow() - val isEnabled = walletResponse.notifyStatus - setNotificationsEnabledLocally(userWalletId, isEnabled) - return isEnabled - } - - private suspend fun setNotificationsEnabledLocally(userWalletId: UserWalletId, isEnabled: Boolean) { - appPreferencesStore.editData { - it.setObjectMap( - key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY, - value = it.getObjectMap(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) - .plus(userWalletId.stringValue to isEnabled), - ) - } - } } \ No newline at end of file diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt index af1295b684..1d130bcda4 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt @@ -5,7 +5,6 @@ import androidx.datastore.preferences.core.Preferences import com.squareup.moshi.Moshi import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.api.tangemTech.models.WalletBody import com.tangem.datasource.api.tangemTech.models.WalletResponse import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys @@ -35,10 +34,6 @@ class DefaultWalletsRepositoryTest { private lateinit var dispatchers: CoroutineDispatcherProvider private val testWalletId = UserWalletId("1234567890abcdef") - private val testWalletResponse = WalletResponse( - id = testWalletId.stringValue, - notifyStatus = true, - ) @Before fun setup() { @@ -54,29 +49,6 @@ class DefaultWalletsRepositoryTest { ) } - @Test - fun `GIVEN force is true WHEN isNotificationsEnabled THEN should fetch from API and update local storage`() = - runTest { - // GIVEN - coEvery { tangemTechApi.getWalletById(testWalletId.stringValue) } returns ApiResponse.Success( - testWalletResponse, - ) - coEvery { preferencesDataStore.updateData(any()) } returns mockk() - - // WHEN - val result = repository.isNotificationsEnabled(testWalletId, force = true) - - // THEN - assertThat(result).isTrue() - coVerify(exactly = 1) { - tangemTechApi.getWalletById(testWalletId.stringValue) - } - coVerify(exactly = 0) { - tangemTechApi.setNotificationsEnabled(any(), any()) - } - coVerify(exactly = 1) { preferencesDataStore.updateData(any()) } - } - @Test fun `GIVEN local storage has value WHEN isNotificationsEnabled THEN should return local value`() = runTest { // GIVEN @@ -84,96 +56,55 @@ class DefaultWalletsRepositoryTest { val preferences = mockk() coEvery { preferencesDataStore.data } returns flowOf(preferences) coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns expectedPreferences - coEvery { tangemTechApi.getWalletById(any()) } returns ApiResponse.Success(testWalletResponse) // WHEN - val result = repository.isNotificationsEnabled(testWalletId, force = false) + val result = repository.isNotificationsEnabled(testWalletId) // THEN assertThat(result).isTrue() - coVerify(inverse = true) { - tangemTechApi.getWalletById(any()) - tangemTechApi.setNotificationsEnabled(any(), any()) - } } @Test - fun `GIVEN local storage is empty WHEN isNotificationsEnabled THEN should fetch from API and update local storage`() = runTest { + fun `GIVEN local storage is empty WHEN isNotificationsEnabled THEN should return false`() = runTest { // GIVEN val preferences = mockk() coEvery { preferencesDataStore.data } returns flowOf(preferences) coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}" - coEvery { tangemTechApi.getWalletById(testWalletId.stringValue) } returns ApiResponse.Success( - testWalletResponse, - ) - coEvery { preferencesDataStore.updateData(any()) } returns mockk() // WHEN - val result = repository.isNotificationsEnabled(testWalletId, force = false) + val result = repository.isNotificationsEnabled(testWalletId) // THEN - assertThat(result).isTrue() - coVerify(exactly = 1) { - tangemTechApi.getWalletById(testWalletId.stringValue) - } - coVerify(exactly = 0) { - tangemTechApi.setNotificationsEnabled(any(), any()) - } - coVerify(exactly = 1) { preferencesDataStore.updateData(any()) } + assertThat(result).isFalse() } @Test - fun `GIVEN enabled status WHEN setNotificationsEnabled THEN should update API and local storage`() = runTest { + fun `GIVEN enabled status WHEN setNotificationsEnabled THEN should update local storage`() = runTest { // GIVEN - coEvery { - tangemTechApi.setNotificationsEnabled( - eq(testWalletId.stringValue), - eq(WalletBody(notifyStatus = true)), - ) - } returns ApiResponse.Success(Unit) - coEvery { preferencesDataStore.updateData(any()) } returns mockk() + val preferences = mockk() + coEvery { preferencesDataStore.data } returns flowOf(preferences) + coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}" + coEvery { preferencesDataStore.updateData(any()) } returns mockk() // WHEN repository.setNotificationsEnabled(testWalletId, isEnabled = true) // THEN - coVerify(exactly = 1) { - tangemTechApi.setNotificationsEnabled( - eq(testWalletId.stringValue), - eq(WalletBody(notifyStatus = true)), - ) - } coVerify(exactly = 1) { preferencesDataStore.updateData(any()) } } @Test - fun `GIVEN disabled status WHEN setNotificationsEnabled THEN should update API and local storage`() = runTest { + fun `GIVEN disabled status WHEN setNotificationsEnabled THEN should update local storage`() = runTest { // GIVEN - coEvery { - tangemTechApi.setNotificationsEnabled( - eq(testWalletId.stringValue), - eq(WalletBody(notifyStatus = false)), - ) - } returns ApiResponse.Success(Unit) - coEvery { preferencesDataStore.updateData(any()) } returns mockk() + val preferences = mockk() + coEvery { preferencesDataStore.data } returns flowOf(preferences) + coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}" + coEvery { preferencesDataStore.updateData(any()) } returns mockk() // WHEN repository.setNotificationsEnabled(testWalletId, isEnabled = false) // THEN - coVerifyOrder { - tangemTechApi.setNotificationsEnabled( - eq(testWalletId.stringValue), - eq(WalletBody(notifyStatus = false)), - ) - preferencesDataStore.updateData(any()) - } - coVerify(exactly = 1) { - tangemTechApi.setNotificationsEnabled( - eq(testWalletId.stringValue), - eq(WalletBody(notifyStatus = false)), - ) - } coVerify(exactly = 1) { preferencesDataStore.updateData(any()) } } @@ -194,7 +125,7 @@ class DefaultWalletsRepositoryTest { ), ) coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) - coEvery { preferencesDataStore.updateData(any()) } returns mockk() + coEvery { preferencesDataStore.updateData(any()) } returns mockk() // WHEN val result = repository.getWalletsInfo(applicationId, updateCache = true) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt index eaf24f6cfa..aec006e796 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt @@ -41,10 +41,8 @@ interface WalletsRepository { suspend fun disableNFT(userWalletId: UserWalletId) - @Throws - suspend fun isNotificationsEnabled(userWalletId: UserWalletId, force: Boolean): Boolean + suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean - @Throws suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) @Throws diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsNotificationsEnabledUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsNotificationsEnabledUseCase.kt index 9db6370c5c..32ff553c46 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsNotificationsEnabledUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsNotificationsEnabledUseCase.kt @@ -8,10 +8,9 @@ class GetIsNotificationsEnabledUseCase( private val walletsRepository: WalletsRepository, ) { - suspend operator fun invoke(userWalletId: UserWalletId, force: Boolean): Either = Either.catch { + suspend operator fun invoke(userWalletId: UserWalletId): Either = Either.catch { walletsRepository.isNotificationsEnabled( userWalletId = userWalletId, - force = force, ) } } \ No newline at end of file