Updated on 2026-08-14
This commit is contained in:
commit
5ee5b4f61b
563 changed files with 13917 additions and 4692 deletions
|
|
@ -24,7 +24,9 @@ dependencies {
|
|||
// region Project - Domain
|
||||
api(projects.domain.account)
|
||||
api(projects.domain.card)
|
||||
api(projects.domain.common)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.tokens)
|
||||
// endregion
|
||||
|
||||
// region Project - Data
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ internal class AccountListConverter @AssistedInject constructor(
|
|||
|
||||
override fun convert(value: GetWalletAccountsResponse): AccountList {
|
||||
return AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
accounts = value.accounts.map(cryptoPortfolioConverter::convert).toSet(),
|
||||
totalAccounts = value.wallet.totalAccounts,
|
||||
sortType = TokensSortTypeConverter.convert(value.wallet.sort),
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ internal class CryptoPortfolioConverter @AssistedInject constructor(
|
|||
derivationIndex = value.derivationIndex.value,
|
||||
icon = value.icon.value.name,
|
||||
iconColor = value.icon.color.name,
|
||||
tokens = value.cryptoCurrencies.map(userTokensResponseFactory::createResponseToken),
|
||||
tokens = value.cryptoCurrencies.map {
|
||||
userTokensResponseFactory.createResponseToken(currency = it, accountId = value.accountId)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.data.account.converter
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -21,8 +20,8 @@ internal object SaveWalletAccountsResponseConverter : Converter<AccountList, Sav
|
|||
)
|
||||
}
|
||||
|
||||
private fun toDTO(account: Account.CryptoPortfolio): WalletAccountDTO {
|
||||
return WalletAccountDTO(
|
||||
private fun toDTO(account: Account.CryptoPortfolio): SaveWalletAccountsResponse.AccountDTO {
|
||||
return SaveWalletAccountsResponse.AccountDTO(
|
||||
id = account.accountId.value,
|
||||
name = AccountNameConverter.convert(value = account.accountName),
|
||||
derivationIndex = account.derivationIndex.value,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ internal object AccountDataModule {
|
|||
walletAccountsSaver: WalletAccountsSaver,
|
||||
accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
userTokensSaver: UserTokensSaver,
|
||||
eTagsStore: ETagsStore,
|
||||
accountConverterFactoryContainer: AccountConverterFactoryContainer,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -51,6 +52,7 @@ internal object AccountDataModule {
|
|||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
archivedAccountsStoreFactory = ArchivedAccountsStoreFactory,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensSaver = userTokensSaver,
|
||||
eTagsStore = eTagsStore,
|
||||
convertersContainer = accountConverterFactoryContainer,
|
||||
dispatchers = dispatchers,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.data.account.di
|
||||
|
||||
import com.tangem.data.account.producer.AccountListCryptoCurrenciesProducer
|
||||
import com.tangem.data.account.producer.DefaultMultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object MultiWalletCryptoCurrenciesProducerModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMultiWalletCryptoCurrenciesProducerFactory(
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
defaultImpl: DefaultMultiWalletCryptoCurrenciesProducer.Factory,
|
||||
accountsImpl: AccountListCryptoCurrenciesProducer.Factory,
|
||||
): MultiWalletCryptoCurrenciesProducer.Factory {
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) accountsImpl else defaultImpl
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.account.fetcher
|
|||
|
||||
import com.tangem.data.account.store.AccountsResponseStore
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.account.utils.assignTokens
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
|
|
@ -14,6 +15,7 @@ import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.
|
|||
import com.tangem.datasource.api.common.response.ETAG_HEADER
|
||||
import com.tangem.datasource.api.common.response.isNetworkError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
|
|
@ -31,17 +33,20 @@ import javax.inject.Singleton
|
|||
* @property accountsResponseStoreFactory factory to create [AccountsResponseStore]
|
||||
* @property userTokensSaver saves user tokens to the database
|
||||
* @property fetchWalletAccountsErrorHandler handles errors during fetching wallet accounts
|
||||
* @property defaultWalletAccountsResponseFactory creates [GetWalletAccountsResponse] from [UserTokensResponse]
|
||||
* @property eTagsStore store for ETags to manage caching
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Singleton
|
||||
internal class DefaultWalletAccountsFetcher @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler,
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory,
|
||||
private val eTagsStore: ETagsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAccountsFetcher, WalletAccountsSaver {
|
||||
|
|
@ -49,30 +54,33 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
override suspend fun fetch(userWalletId: UserWalletId) {
|
||||
val savedAccountsResponse = getAccountsResponseStore(userWalletId = userWalletId).getSyncOrNull()
|
||||
val accountsResponse = fetchWalletAccounts(userWalletId, savedAccountsResponse)
|
||||
val unassignedTokens = accountsResponse?.unassignedTokens
|
||||
?: return
|
||||
|
||||
if (!unassignedTokens.isNullOrEmpty()) {
|
||||
if (accountsResponse.accounts.isEmpty()) {
|
||||
initializeAccounts(userWalletId, accountsResponse)
|
||||
} else if (accountsResponse.unassignedTokens.isNotEmpty()) {
|
||||
assignTokens(userWalletId, accountsResponse)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun pushAndStore(userWalletId: UserWalletId, response: GetWalletAccountsResponse) {
|
||||
push(userWalletId = userWalletId, accounts = response.accounts)
|
||||
store(userWalletId = userWalletId, response = response)
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, response: GetWalletAccountsResponse) {
|
||||
val store = getAccountsResponseStore(userWalletId = userWalletId)
|
||||
|
||||
store.updateData { response }
|
||||
}
|
||||
|
||||
override suspend fun push(userWalletId: UserWalletId, accounts: List<WalletAccountDTO>) {
|
||||
push(userWalletId = userWalletId, body = SaveWalletAccountsResponse(accounts = accounts))
|
||||
override suspend fun push(
|
||||
userWalletId: UserWalletId,
|
||||
accounts: List<WalletAccountDTO>,
|
||||
): GetWalletAccountsResponse? {
|
||||
return push(userWalletId = userWalletId, body = SaveWalletAccountsResponse(accounts = accounts))
|
||||
}
|
||||
|
||||
override suspend fun push(userWalletId: UserWalletId, body: SaveWalletAccountsResponse) {
|
||||
safeApiCall(
|
||||
override suspend fun push(
|
||||
userWalletId: UserWalletId,
|
||||
body: SaveWalletAccountsResponse,
|
||||
): GetWalletAccountsResponse? {
|
||||
return safeApiCall(
|
||||
call = {
|
||||
var eTag = getETag(userWalletId)
|
||||
|
||||
|
|
@ -86,11 +94,7 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = body.copy(
|
||||
accounts = body.accounts.map {
|
||||
it.copy(tokens = null, totalTokens = null, totalNetworks = null)
|
||||
},
|
||||
),
|
||||
body = body,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +106,8 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
if (error.isNetworkError(code = Code.PRECONDITION_FAILED)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -135,12 +141,28 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
pushWalletAccounts = ::push,
|
||||
storeWalletAccounts = ::store,
|
||||
)
|
||||
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun initializeAccounts(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) {
|
||||
val response = defaultWalletAccountsResponseFactory.create(
|
||||
userWalletId = userWalletId,
|
||||
userTokensResponse = UserTokensResponse(
|
||||
group = accountsResponse.wallet.group,
|
||||
sort = accountsResponse.wallet.sort,
|
||||
tokens = accountsResponse.unassignedTokens,
|
||||
),
|
||||
)
|
||||
|
||||
userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse())
|
||||
val syncedResponse = push(userWalletId = userWalletId, accounts = response.accounts)
|
||||
|
||||
if (syncedResponse != null) {
|
||||
store(userWalletId = userWalletId, response = syncedResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun assignTokens(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) {
|
||||
val accountsResponseWithTokens = accountsResponse.assignTokens(userWalletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.data.account.fetcher
|
||||
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.account.utils.assignTokens
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseAccountIdEnricher
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code
|
||||
|
|
@ -13,10 +11,6 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -24,12 +18,9 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Handles errors that occur during the fetching of wallet accounts
|
||||
*
|
||||
* @property userTokensSaver saves user tokens to the storage
|
||||
* @property userWalletsStore provides access to user wallet data
|
||||
* @property userTokensResponseStore provides access to user token responses.
|
||||
* @property cryptoPortfolioCF factory for converting crypto portfolios
|
||||
* @property userTokensResponseFactory factory for creating user token responses
|
||||
* @property cardCryptoCurrencyFactory factory for creating default cryptocurrencies for multi-currency wallets
|
||||
* @property userTokensSaver saves user tokens to the storage
|
||||
* @property userTokensResponseStore provides access to user token responses.
|
||||
* @property defaultWalletAccountsResponseFactory creates [GetWalletAccountsResponse] from [UserTokensResponse]
|
||||
*
|
||||
* @see DefaultWalletAccountsFetcher
|
||||
*
|
||||
|
|
@ -37,11 +28,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class FetchWalletAccountsErrorHandler @Inject constructor(
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
|
||||
private val userTokensResponseFactory: UserTokensResponseFactory,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -59,72 +47,47 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
|
|||
error: ApiResponseError,
|
||||
userWalletId: UserWalletId,
|
||||
savedAccountsResponse: GetWalletAccountsResponse?,
|
||||
pushWalletAccounts: suspend (userWalletId: UserWalletId, accounts: List<WalletAccountDTO>) -> Unit,
|
||||
storeWalletAccounts: suspend (userWalletId: UserWalletId, response: GetWalletAccountsResponse) -> Unit,
|
||||
) {
|
||||
pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> GetWalletAccountsResponse?,
|
||||
storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit,
|
||||
): GetWalletAccountsResponse? {
|
||||
val isResponseUpToDate = error.isNetworkError(code = Code.NOT_MODIFIED)
|
||||
if (isResponseUpToDate) {
|
||||
Timber.e("ETag is up to date, no need to update accounts for wallet: $userWalletId")
|
||||
return
|
||||
return savedAccountsResponse
|
||||
}
|
||||
|
||||
val (accountDTOs, userTokensResponse) = if (savedAccountsResponse == null) {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
|
||||
createDefaultAccountDTOs(userWallet) to getFromLegacyStore(userWalletId).orDefault(userWallet)
|
||||
} else {
|
||||
savedAccountsResponse.accounts to savedAccountsResponse.toUserTokensResponse()
|
||||
}
|
||||
var response = savedAccountsResponse ?: createDefaultResponse(userWalletId)
|
||||
val (accountDTOs, userTokensResponse) = response.accounts to response.toUserTokensResponse()
|
||||
|
||||
val isNotFoundError = error.isNetworkError(code = Code.NOT_FOUND)
|
||||
if (isNotFoundError) {
|
||||
pushWalletAccounts(userWalletId, accountDTOs)
|
||||
userTokensSaver.push(userWalletId = userWalletId, response = userTokensResponse)
|
||||
val updatedResponse = pushWalletAccounts(userWalletId, accountDTOs)
|
||||
|
||||
if (updatedResponse != null) {
|
||||
response = updatedResponse
|
||||
}
|
||||
}
|
||||
|
||||
val response = savedAccountsResponse.orDefault(userWalletId, accountDTOs, userTokensResponse)
|
||||
storeWalletAccounts(userWalletId, response)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun createDefaultAccountDTOs(userWallet: UserWallet): List<WalletAccountDTO> {
|
||||
val accounts = AccountList.empty(userWallet).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val converter = cryptoPortfolioCF.create(userWallet = userWallet)
|
||||
|
||||
return converter.convertListBack(input = accounts)
|
||||
private suspend fun createDefaultResponse(userWalletId: UserWalletId): GetWalletAccountsResponse {
|
||||
return defaultWalletAccountsResponseFactory.create(
|
||||
userWalletId = userWalletId,
|
||||
userTokensResponse = getFromLegacyStore(userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getFromLegacyStore(userWalletId: UserWalletId): UserTokensResponse? {
|
||||
return userTokensResponseStore.getSyncOrNull(userWalletId)
|
||||
?.let {
|
||||
it.copy(
|
||||
tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = it.tokens),
|
||||
)
|
||||
}
|
||||
.also { userTokensResponseStore.clear(userWalletId) }
|
||||
}
|
||||
|
||||
private fun UserTokensResponse?.orDefault(userWallet: UserWallet): UserTokensResponse {
|
||||
if (this != null) return this
|
||||
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet = userWallet),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun GetWalletAccountsResponse?.orDefault(
|
||||
userWalletId: UserWalletId,
|
||||
accountDTOs: List<WalletAccountDTO>,
|
||||
userTokensResponse: UserTokensResponse,
|
||||
): GetWalletAccountsResponse {
|
||||
if (this != null) return this
|
||||
|
||||
return GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = userTokensResponse.group,
|
||||
sort = userTokensResponse.sort,
|
||||
totalAccounts = accountDTOs.size,
|
||||
),
|
||||
accounts = accountDTOs.assignTokens(userWalletId = userWalletId, tokens = userTokensResponse.tokens),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.data.account.producer
|
||||
|
||||
import arrow.core.Option
|
||||
import arrow.core.some
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Implementation of [MultiWalletCryptoCurrenciesProducer] that produces crypto currencies of all accounts
|
||||
*
|
||||
* @property params params
|
||||
* @property userWalletsStore UserWallet's store
|
||||
* @property accountsResponseStoreFactory factory to create store with accounts response
|
||||
* @property responseCryptoCurrenciesFactory factory for creating [CryptoCurrency] from `UserTokensResponse`
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor(
|
||||
@Assisted val params: MultiWalletCryptoCurrenciesProducer.Params,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiWalletCryptoCurrenciesProducer {
|
||||
|
||||
override val fallback: Option<Set<CryptoCurrency>> = emptySet<CryptoCurrency>().some()
|
||||
|
||||
override fun produce(): Flow<Set<CryptoCurrency>> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
|
||||
|
||||
if (!userWallet.isMultiCurrency) {
|
||||
error("${this::class.simpleName} supports only multi-currency wallet")
|
||||
}
|
||||
|
||||
return accountsResponseStoreFactory.create(userWalletId = userWallet.walletId).data
|
||||
.distinctUntilChanged()
|
||||
.map { response ->
|
||||
if (response == null) return@map emptySet()
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = response.toUserTokensResponse(),
|
||||
userWallet = userWallet,
|
||||
).toSet()
|
||||
}
|
||||
.onEmpty { emit(emptySet()) }
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MultiWalletCryptoCurrenciesProducer.Factory {
|
||||
override fun create(params: MultiWalletCryptoCurrenciesProducer.Params): AccountListCryptoCurrenciesProducer
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import arrow.core.some
|
|||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.MultiAccountListProducer
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -35,10 +36,11 @@ internal class DefaultMultiAccountListProducer @AssistedInject constructor(
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun produce(): Flow<List<AccountList>> {
|
||||
return userWalletsStore.userWallets
|
||||
.map { it.map(UserWallet::walletId) }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { userWallets ->
|
||||
.flatMapLatest { ids ->
|
||||
combine(
|
||||
flows = userWallets.map(walletAccountListFlowFactory::create),
|
||||
flows = ids.map(walletAccountListFlowFactory::create),
|
||||
transform = ::listOf,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.data.tokens
|
||||
package com.tangem.data.account.producer
|
||||
|
||||
import arrow.core.Option
|
||||
import arrow.core.some
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.account.producer
|
|||
|
||||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -11,16 +10,13 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
/**
|
||||
* Default implementation of [SingleAccountListProducer].
|
||||
* Produces a list of [AccountList] for a specific user wallet.
|
||||
*
|
||||
* @property params params containing the user wallet ID
|
||||
* @property userWalletsStore store that provides user wallets
|
||||
* @property walletAccountListFlowFactory builder to create flows of [AccountList] for each wallet
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
*
|
||||
|
|
@ -28,7 +24,6 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||
*/
|
||||
internal class DefaultSingleAccountListProducer @AssistedInject constructor(
|
||||
@Assisted val params: SingleAccountListProducer.Params,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val walletAccountListFlowFactory: WalletAccountListFlowFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleAccountListProducer {
|
||||
|
|
@ -37,11 +32,7 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun produce(): Flow<AccountList> {
|
||||
return userWalletsStore.userWallets
|
||||
.mapNotNull { userWallets ->
|
||||
userWallets.firstOrNull { it.walletId == params.userWalletId }
|
||||
}
|
||||
.flatMapLatest(walletAccountListFlowFactory::create)
|
||||
return walletAccountListFlowFactory.create(userWalletId = params.userWalletId)
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import com.tangem.data.account.converter.AccountListConverter
|
|||
import com.tangem.data.account.store.AccountsResponseStore
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -22,12 +24,15 @@ import javax.inject.Inject
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletAccountListFlowFactory @Inject constructor(
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val accountListConverterFactory: AccountListConverter.Factory,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet): Flow<AccountList> {
|
||||
fun create(userWalletId: UserWalletId): Flow<AccountList> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(userWalletId)
|
||||
|
||||
return if (userWallet.isMultiCurrency) {
|
||||
createForMultiWallet(userWallet)
|
||||
} else {
|
||||
|
|
@ -53,6 +58,6 @@ internal class WalletAccountListFlowFactory @Inject constructor(
|
|||
setOf(cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet = userWallet))
|
||||
}
|
||||
|
||||
return AccountList.empty(userWallet = userWallet, cryptoCurrencies = currencies)
|
||||
return AccountList.empty(userWalletId = userWallet.walletId, cryptoCurrencies = currencies)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,10 @@ import com.tangem.data.account.store.AccountsResponseStore
|
|||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.store.ArchivedAccountsStore
|
||||
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.data.common.cache.etag.ETagsStore
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
|
|
@ -24,9 +26,11 @@ import com.tangem.domain.models.account.AccountId
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.replaceBy
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -38,6 +42,7 @@ internal class DefaultAccountsCRUDRepository(
|
|||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val archivedAccountsStoreFactory: ArchivedAccountsStoreFactory,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val eTagsStore: ETagsStore,
|
||||
private val convertersContainer: AccountConverterFactoryContainer,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -101,12 +106,44 @@ internal class DefaultAccountsCRUDRepository(
|
|||
}
|
||||
|
||||
override suspend fun saveAccounts(accountList: AccountList) {
|
||||
val userWalletId = accountList.userWallet.walletId
|
||||
val converter = convertersContainer.createCryptoPortfolioConverter(userWalletId = accountList.userWalletId)
|
||||
|
||||
val converter = convertersContainer.getWalletAccountsResponseCF.create(userWallet = accountList.userWallet)
|
||||
val accountsResponse = converter.convert(value = accountList)
|
||||
val accountDTOs = converter.convertListBack(
|
||||
input = accountList.accounts.filterIsInstance<Account.CryptoPortfolio>(),
|
||||
)
|
||||
|
||||
walletAccountsSaver.pushAndStore(userWalletId = userWalletId, response = accountsResponse)
|
||||
val syncedResponse = walletAccountsSaver.push(userWalletId = accountList.userWalletId, accounts = accountDTOs)
|
||||
if (syncedResponse != null) {
|
||||
walletAccountsSaver.store(userWalletId = accountList.userWalletId, response = syncedResponse)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveAccount(account: Account.CryptoPortfolio) {
|
||||
val store = getAccountsResponseStore(userWalletId = account.userWalletId)
|
||||
|
||||
val converter = convertersContainer.createCryptoPortfolioConverter(userWalletId = account.userWalletId)
|
||||
val newAccountDTO = converter.convertBack(value = account)
|
||||
|
||||
store.updateData { response ->
|
||||
response ?: return@updateData response
|
||||
|
||||
response.copy(
|
||||
accounts = response.accounts.toMutableList().apply {
|
||||
replaceBy(newAccountDTO) { it.id == newAccountDTO.id }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun syncTokens(userWalletId: UserWalletId) {
|
||||
val response = getAccountsResponseSync(userWalletId = userWalletId)
|
||||
|
||||
if (response == null) {
|
||||
Timber.e("Can't sync tokens. No accounts response found for wallet: $userWalletId")
|
||||
return
|
||||
}
|
||||
|
||||
userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse())
|
||||
}
|
||||
|
||||
override suspend fun getTotalAccountsCountSync(userWalletId: UserWalletId): Option<Int> = option {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.data.account.utils
|
||||
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Factory to create default [GetWalletAccountsResponse].
|
||||
*
|
||||
* @property userWalletsListRepository repository to get user wallet information
|
||||
* @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts
|
||||
* @property userTokensResponseFactory factory to create [UserTokensResponse]
|
||||
* @property cardCryptoCurrencyFactory factory to get default coins for multi-currency wallet
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultWalletAccountsResponseFactory @Inject constructor(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
|
||||
private val userTokensResponseFactory: UserTokensResponseFactory,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
) {
|
||||
|
||||
suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse {
|
||||
val userWallet = userWalletsListRepository.userWalletsSync().firstOrNull { it.walletId == userWalletId }
|
||||
|
||||
val accountDTOs = userWallet?.let(::createDefaultAccountDTOs).orEmpty()
|
||||
val response = userTokensResponse.orDefault(userWallet = userWallet)
|
||||
|
||||
return GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = response.group,
|
||||
sort = response.sort,
|
||||
totalAccounts = accountDTOs.size,
|
||||
),
|
||||
accounts = accountDTOs.assignTokens(userWalletId = userWalletId, tokens = response.tokens),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDefaultAccountDTOs(userWallet: UserWallet): List<WalletAccountDTO> {
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val converter = cryptoPortfolioCF.create(userWallet = userWallet)
|
||||
|
||||
return converter.convertListBack(input = accounts)
|
||||
}
|
||||
|
||||
private fun UserTokensResponse?.orDefault(userWallet: UserWallet?): UserTokensResponse {
|
||||
if (this != null) return this
|
||||
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = userWallet?.let(cardCryptoCurrencyFactory::createDefaultCoinsForMultiCurrencyWallet).orEmpty(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import com.tangem.domain.models.TokensGroupType
|
|||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal fun createWalletAccountDTO(
|
||||
|
|
@ -72,13 +71,13 @@ internal fun createGetWalletAccountsResponse(
|
|||
}
|
||||
|
||||
internal fun createAccountList(
|
||||
userWallet: UserWallet,
|
||||
userWalletId: UserWalletId,
|
||||
sortType: TokensSortType = TokensSortType.BALANCE,
|
||||
groupType: TokensGroupType = TokensGroupType.NETWORK,
|
||||
): AccountList {
|
||||
return AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(createCryptoPortfolio(userWallet.walletId)),
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(createCryptoPortfolio(userWalletId)),
|
||||
totalAccounts = 1,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class AccountListConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.BALANCE,
|
||||
groupType = TokensGroupType.NETWORK,
|
||||
),
|
||||
|
|
@ -110,7 +110,7 @@ class AccountListConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
|
|
@ -124,7 +124,7 @@ class AccountListConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class GetWalletAccountsResponseConverterTest {
|
|||
@Test
|
||||
fun `cryptoPortfolioConverter throws exception`() {
|
||||
// Arrange
|
||||
val domain = createAccountList(userWallet = userWallet)
|
||||
val domain = createAccountList(userWalletId = userWallet.walletId)
|
||||
val exception = IllegalStateException("Test exception")
|
||||
|
||||
every { cryptoPortfolioConverter.convertBack(any()) } throws exception
|
||||
|
|
@ -92,7 +92,7 @@ class GetWalletAccountsResponseConverterTest {
|
|||
return listOf(
|
||||
ConvertModel(
|
||||
value = createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.BALANCE,
|
||||
groupType = TokensGroupType.NETWORK,
|
||||
),
|
||||
|
|
@ -106,7 +106,7 @@ class GetWalletAccountsResponseConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,14 +2,10 @@ package com.tangem.data.account.converter
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
|
|
@ -19,13 +15,11 @@ class SaveWalletAccountsResponseConverterTest {
|
|||
@Test
|
||||
fun convert() {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns UserWalletId("011")
|
||||
}
|
||||
val userWalletId = UserWalletId("011")
|
||||
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWallet.walletId)),
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
|
@ -36,7 +30,7 @@ class SaveWalletAccountsResponseConverterTest {
|
|||
// Assert
|
||||
val expected = SaveWalletAccountsResponse(
|
||||
accounts = listOf(
|
||||
WalletAccountDTO(
|
||||
SaveWalletAccountsResponse.AccountDTO(
|
||||
id = accountList.mainAccount.accountId.value,
|
||||
name = (accountList.mainAccount.accountName as? AccountName.Custom)?.value,
|
||||
derivationIndex = accountList.mainAccount.derivationIndex.value,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.account.converter.createGetWalletAccountsResponse
|
|||
import com.tangem.data.account.converter.createWalletAccountDTO
|
||||
import com.tangem.data.account.store.AccountsResponseStore
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.common.cache.etag.ETagsStore
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
|
|
@ -36,6 +37,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
|
||||
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
|
||||
private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler = mockk(relaxUnitFun = true)
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory = mockk()
|
||||
private val eTagsStore: ETagsStore = mockk(relaxUnitFun = true)
|
||||
|
||||
private val fetcher: DefaultWalletAccountsFetcher = DefaultWalletAccountsFetcher(
|
||||
|
|
@ -43,6 +45,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
userTokensSaver = userTokensSaver,
|
||||
fetchWalletAccountsErrorHandler = fetchWalletAccountsErrorHandler,
|
||||
defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory,
|
||||
eTagsStore = eTagsStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
|
@ -114,7 +117,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
eTag = eTag,
|
||||
body = SaveWalletAccountsResponse(updatedAccountsResponse.accounts),
|
||||
)
|
||||
} returns ApiResponse.Success(data = Unit)
|
||||
} returns ApiResponse.Success(data = updatedAccountsResponse)
|
||||
|
||||
// Act
|
||||
fetcher.fetch(userWalletId)
|
||||
|
|
@ -205,6 +208,16 @@ class DefaultWalletAccountsFetcherTest {
|
|||
tangemTechApi.getWalletAccounts(walletId = userWalletId.stringValue, eTag = eTag)
|
||||
} returns apiError as ApiResponse<GetWalletAccountsResponse>
|
||||
|
||||
coEvery {
|
||||
fetchWalletAccountsErrorHandler.handle(
|
||||
error = apiError.cause,
|
||||
userWalletId = userWalletId,
|
||||
savedAccountsResponse = null,
|
||||
pushWalletAccounts = any(),
|
||||
storeWalletAccounts = any(),
|
||||
)
|
||||
} returns savedAccountsResponse
|
||||
|
||||
// Act
|
||||
fetcher.fetch(userWalletId)
|
||||
|
||||
|
|
@ -260,26 +273,26 @@ class DefaultWalletAccountsFetcherTest {
|
|||
@Test
|
||||
fun `push should call saveWalletAccounts with correct params`() = runTest {
|
||||
// Arrange
|
||||
val accounts = listOf(createWalletAccountDTO(userWalletId = userWalletId, tokens = null))
|
||||
val response = SaveWalletAccountsResponse(accounts)
|
||||
val getResponse = createGetWalletAccountsResponse(userWalletId, tokens = null)
|
||||
val saveResponse = SaveWalletAccountsResponse(getResponse.accounts)
|
||||
|
||||
coEvery {
|
||||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = response,
|
||||
body = saveResponse,
|
||||
)
|
||||
} returns ApiResponse.Success(data = Unit)
|
||||
} returns ApiResponse.Success(data = getResponse)
|
||||
|
||||
// Act
|
||||
fetcher.push(userWalletId, response)
|
||||
fetcher.push(userWalletId, saveResponse)
|
||||
|
||||
// Assert
|
||||
coVerify {
|
||||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = response,
|
||||
body = saveResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -303,7 +316,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
eTag = eTag,
|
||||
body = response,
|
||||
)
|
||||
} returns saveApiResponse as ApiResponse<Unit>
|
||||
} returns saveApiResponse as ApiResponse<GetWalletAccountsResponse>
|
||||
|
||||
// Act
|
||||
val actual = runCatching { fetcher.push(userWalletId, response) }.exceptionOrNull()!!
|
||||
|
|
@ -313,42 +326,6 @@ class DefaultWalletAccountsFetcherTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class PushAndStore {
|
||||
|
||||
@Test
|
||||
fun `pushAndStore should call push and store with correct params`() = runTest {
|
||||
// Arrange
|
||||
val accounts = listOf(createWalletAccountDTO(userWalletId = userWalletId, tokens = null))
|
||||
val response = createGetWalletAccountsResponse(userWalletId).copy(accounts = accounts)
|
||||
|
||||
coEvery {
|
||||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = SaveWalletAccountsResponse(accounts = response.accounts),
|
||||
)
|
||||
} returns ApiResponse.Success(data = Unit)
|
||||
|
||||
coEvery { accountsResponseStore.updateData(any()) } returns mockk()
|
||||
|
||||
// Act
|
||||
fetcher.pushAndStore(userWalletId, response)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = SaveWalletAccountsResponse(accounts = response.accounts),
|
||||
)
|
||||
accountsResponseStoreFactory.create(userWalletId)
|
||||
accountsResponseStore.updateData(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createToken(
|
||||
networkId: String = "ethereum",
|
||||
derivationPath: String = "m/44'/60'/0'/0/0",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.data.account.fetcher
|
||||
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.account.converter.createGetWalletAccountsResponse
|
||||
import com.tangem.data.account.converter.createWalletAccountDTO
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code
|
||||
|
|
@ -11,12 +11,11 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -29,35 +28,25 @@ import org.junit.jupiter.api.TestInstance
|
|||
class FetchWalletAccountsErrorHandlerTest {
|
||||
|
||||
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true)
|
||||
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory = mockk()
|
||||
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
|
||||
private val userTokensResponseFactory: UserTokensResponseFactory = mockk()
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory = mockk()
|
||||
|
||||
private val handler = FetchWalletAccountsErrorHandler(
|
||||
userTokensSaver = userTokensSaver,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
cryptoPortfolioCF = cryptoPortfolioCF,
|
||||
userTokensResponseFactory = userTokensResponseFactory,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory,
|
||||
)
|
||||
|
||||
private val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
private val pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> GetWalletAccountsResponse =
|
||||
mockk(relaxed = true)
|
||||
private val storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit = mockk(relaxed = true)
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
clearMocks(
|
||||
userTokensSaver,
|
||||
userWalletsStore,
|
||||
userTokensResponseStore,
|
||||
cryptoPortfolioCF,
|
||||
cryptoPortfolioConverter,
|
||||
cardCryptoCurrencyFactory,
|
||||
defaultWalletAccountsResponseFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -70,9 +59,6 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
errorBody = null,
|
||||
)
|
||||
|
||||
val pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> Unit = mockk()
|
||||
val storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit = mockk()
|
||||
|
||||
// Act
|
||||
handler.handle(
|
||||
error = error,
|
||||
|
|
@ -84,12 +70,8 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
// Assert
|
||||
coVerify(inverse = true) {
|
||||
userWalletsStore.getSyncStrict(key = any())
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
userTokensResponseFactory.createUserTokensResponse(any(), any(), any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
cryptoPortfolioCF.create(any())
|
||||
cryptoPortfolioConverter.convertListBack(any())
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId = any(), userTokensResponse = any())
|
||||
pushWalletAccounts(any(), any())
|
||||
userTokensSaver.push(userWalletId = any(), response = any())
|
||||
storeWalletAccounts(any(), any())
|
||||
|
|
@ -105,29 +87,11 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
errorBody = null,
|
||||
)
|
||||
|
||||
val accountDTO = WalletAccountDTO(
|
||||
id = "nibh",
|
||||
name = "Michael Dotson",
|
||||
derivationIndex = 7135,
|
||||
icon = "consectetuer",
|
||||
iconColor = "ferri",
|
||||
tokens = listOf(),
|
||||
totalTokens = 7738,
|
||||
totalNetworks = 3348,
|
||||
)
|
||||
val accountDTO = createWalletAccountDTO(userWalletId)
|
||||
|
||||
val savedAccountsResponse = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = UserTokensResponse.GroupType.NONE,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
totalAccounts = 1,
|
||||
),
|
||||
accounts = listOf(accountDTO),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
val savedAccountsResponse = createGetWalletAccountsResponse(userWalletId)
|
||||
|
||||
val pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> Unit = mockk(relaxed = true)
|
||||
val storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit = mockk(relaxed = true)
|
||||
coEvery { pushWalletAccounts(userWalletId, listOf(accountDTO)) } returns savedAccountsResponse
|
||||
|
||||
// Act
|
||||
handler.handle(
|
||||
|
|
@ -140,18 +104,14 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
// Assert
|
||||
coVerify {
|
||||
pushWalletAccounts(userWalletId, listOf(accountDTO))
|
||||
userTokensSaver.push(userWalletId, response = savedAccountsResponse.toUserTokensResponse())
|
||||
pushWalletAccounts(userWalletId, listOf(accountDTO))
|
||||
storeWalletAccounts(userWalletId, savedAccountsResponse)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userWalletsStore.getSyncStrict(key = any())
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
userTokensResponseFactory.createUserTokensResponse(any(), any(), any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
cryptoPortfolioCF.create(any())
|
||||
cryptoPortfolioConverter.convertListBack(any())
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId = any(), userTokensResponse = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,9 +120,6 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
// Arrange
|
||||
val error = ApiResponseError.TimeoutException()
|
||||
|
||||
val accounts = AccountList.empty(userWallet).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val accountDTO = WalletAccountDTO(
|
||||
id = "nibh",
|
||||
name = "Michael Dotson",
|
||||
|
|
@ -186,21 +143,10 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
val userTokensResponse = savedAccountsResponse.toUserTokensResponse()
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet
|
||||
every { cryptoPortfolioCF.create(userWallet) } returns cryptoPortfolioConverter
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountDTO)
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId) } returns null
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns userTokensResponse
|
||||
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList()
|
||||
|
||||
val pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> Unit = mockk(relaxed = true)
|
||||
val storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit = mockk(relaxed = true)
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId) } returns userTokensResponse
|
||||
coEvery {
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId, userTokensResponse)
|
||||
} returns savedAccountsResponse
|
||||
|
||||
// Act
|
||||
handler.handle(
|
||||
|
|
@ -213,16 +159,8 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
// Assert
|
||||
coVerify {
|
||||
userWalletsStore.getSyncStrict(userWalletId)
|
||||
cryptoPortfolioCF.create(userWallet)
|
||||
cryptoPortfolioConverter.convertListBack(accounts)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId)
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet)
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId, userTokensResponse)
|
||||
storeWalletAccounts(userWalletId, any())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -63,7 +63,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +73,11 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.NONE)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.NONE)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -95,9 +95,9 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,10 +107,10 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -128,9 +128,9 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val exception = RuntimeException("Converter error")
|
||||
every { walletAccountListFlowFactory.create(userWallet) } throws exception
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = producer.produceWithFallback().let(::getEmittedValues)
|
||||
|
|
@ -152,7 +152,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns emptyFlow()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -188,7 +188,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,9 +203,9 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(listOf(userWallet, userWallet2))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
|
||||
every { walletAccountListFlowFactory.create(userWallet2) } returns emptyFlow()
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
|
||||
every { walletAccountListFlowFactory.create(userWalletId2) } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -215,8 +215,8 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWallet2)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
walletAccountListFlowFactory.create(userWalletId2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.data.tokens
|
||||
package com.tangem.data.account.producer
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.account.producer
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
|
|
@ -11,7 +10,6 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -26,7 +24,6 @@ import org.junit.jupiter.api.TestInstance
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultSingleAccountListProducerTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val walletAccountListFlowFactory: WalletAccountListFlowFactory = mockk()
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
|
@ -36,24 +33,22 @@ class DefaultSingleAccountListProducerTest {
|
|||
|
||||
private val producer = DefaultSingleAccountListProducer(
|
||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||
userWalletsStore = userWalletsStore,
|
||||
walletAccountListFlowFactory = walletAccountListFlowFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(userWalletsStore, walletAccountListFlowFactory)
|
||||
clearMocks(walletAccountListFlowFactory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun produce() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
MutableStateFlow(listOf(userWallet))
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -63,22 +58,18 @@ class DefaultSingleAccountListProducerTest {
|
|||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow will updated if factoryFlow is updated`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.NONE)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.NONE)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -95,23 +86,18 @@ class DefaultSingleAccountListProducerTest {
|
|||
Truth.assertThat(secondEmission).containsExactly(updatedAccountList)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is filtered the same response`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -128,71 +114,8 @@ class DefaultSingleAccountListProducerTest {
|
|||
Truth.assertThat(secondEmission).containsExactly(accountList)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if factory throws exception`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val exception = RuntimeException("Converter error")
|
||||
every { walletAccountListFlowFactory.create(userWallet) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = producer.produceWithFallback().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty() // no emissions
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if userWalletsFlow returns empty flow`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = emptyFlow<List<UserWallet>>()
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty() // no emissions
|
||||
|
||||
coVerify(exactly = 1) { userWalletsStore.userWallets }
|
||||
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if userWalletsFlow doesn't contains userWalletId from params`() = runTest {
|
||||
// Arrange
|
||||
val unknownId = UserWalletId("012")
|
||||
val unknownWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns unknownId
|
||||
}
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(unknownWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty() // no emissions
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.data.account.store.AccountsResponseStore
|
|||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -28,6 +29,7 @@ import org.junit.jupiter.api.TestInstance
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class WalletAccountListFlowFactoryTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory = mockk()
|
||||
private val accountsResponseStore: AccountsResponseStore = mockk()
|
||||
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
|
||||
|
|
@ -38,6 +40,7 @@ class WalletAccountListFlowFactoryTest {
|
|||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
|
||||
private val factory = WalletAccountListFlowFactory(
|
||||
userWalletsStore = userWalletsStore,
|
||||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
accountListConverterFactory = accountListConverterFactory,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
|
|
@ -49,6 +52,7 @@ class WalletAccountListFlowFactoryTest {
|
|||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(
|
||||
userWalletsStore,
|
||||
accountsResponseStoreFactory,
|
||||
accountsResponseStore,
|
||||
accountListConverterFactory,
|
||||
|
|
@ -66,17 +70,19 @@ class WalletAccountListFlowFactoryTest {
|
|||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet
|
||||
|
||||
val accountsResponse = createGetWalletAccountsResponse(userWalletId)
|
||||
every { accountsResponseStoreFactory.create(userWalletId) } returns accountsResponseStore
|
||||
every { accountsResponseStore.data } returns accountsResponseStoreFlow
|
||||
accountsResponseStoreFlow.value = accountsResponse
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { accountListConverterFactory.create(userWallet) } returns accountListConverter
|
||||
every { accountListConverter.convert(accountsResponse) } returns accountList
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet).let(::getEmittedValues)
|
||||
val actual = factory.create(userWalletId).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = accountList
|
||||
|
|
@ -99,14 +105,16 @@ class WalletAccountListFlowFactoryTest {
|
|||
fun `create for single wallet`() = runTest {
|
||||
val userWallet = MockUserWalletFactory.create().copy(isMultiCurrency = false)
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWallet.walletId) } returns userWallet
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
every { cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet) } returns currency
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet).let(::getEmittedValues)
|
||||
val actual = factory.create(userWallet.walletId).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = AccountList.empty(userWallet = userWallet, cryptoCurrencies = setOf(currency))
|
||||
val expected = AccountList.empty(userWalletId = userWallet.walletId, cryptoCurrencies = setOf(currency))
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
|
|
@ -126,16 +134,18 @@ class WalletAccountListFlowFactoryTest {
|
|||
fun `flow is created for single wallet with token`() = runTest {
|
||||
val nodl = MockUserWalletFactory.createSingleWalletWithToken()
|
||||
|
||||
every { userWalletsStore.getSyncStrict(nodl.walletId) } returns nodl
|
||||
|
||||
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
|
||||
every {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = nodl)
|
||||
} returns currencies.toList()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(nodl).let(::getEmittedValues)
|
||||
val actual = factory.create(nodl.walletId).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = AccountList.empty(userWallet = nodl, cryptoCurrencies = currencies)
|
||||
val expected = AccountList.empty(userWalletId = nodl.walletId, cryptoCurrencies = currencies)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.account.store.ArchivedAccountsStore
|
|||
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.data.common.cache.etag.ETagsStore
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
|
|
@ -25,7 +26,6 @@ import com.tangem.domain.models.account.AccountId
|
|||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
|
|
@ -37,6 +37,7 @@ import kotlin.time.Duration.Companion.minutes
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("UnusedFlow")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultAccountsCRUDRepositoryTest {
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
private val archivedAccountsStore = ArchivedAccountsStore(runtimeStore = archivedAccountsInnerStore)
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val userTokensSaver: UserTokensSaver = mockk()
|
||||
private val eTagsStore: ETagsStore = mockk()
|
||||
|
||||
private val convertersContainer: AccountConverterFactoryContainer = mockk()
|
||||
|
|
@ -64,6 +66,7 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
archivedAccountsStoreFactory = archivedAccountsStoreFactory,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensSaver = userTokensSaver,
|
||||
eTagsStore = eTagsStore,
|
||||
convertersContainer = convertersContainer,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
@ -580,22 +583,18 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
@Test
|
||||
fun `saveAccounts should call API and update store`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accounts = accountList.accounts.filterIsInstance<CryptoPortfolio>()
|
||||
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
|
||||
val accountsResponse = mockk<GetWalletAccountsResponse>()
|
||||
val accountsResponse = createGetWalletAccountsResponse(userWalletId)
|
||||
accountsResponseStoreFlow.value = accountsResponse
|
||||
|
||||
val converter = mockk<GetWalletAccountsResponseConverter> {
|
||||
every { this@mockk.convert(accountList) } returns accountsResponse
|
||||
val converter = mockk<CryptoPortfolioConverter> {
|
||||
every { this@mockk.convertListBack(accounts) } returns accountsResponse.accounts
|
||||
}
|
||||
|
||||
every {
|
||||
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet)
|
||||
} returns converter
|
||||
every { convertersContainer.createCryptoPortfolioConverter(userWalletId) } returns converter
|
||||
coEvery { walletAccountsSaver.push(userWalletId, accountsResponse.accounts) } returns accountsResponse
|
||||
|
||||
// Act
|
||||
repository.saveAccounts(accountList)
|
||||
|
|
@ -604,35 +603,30 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
Truth.assertThat(accountsResponseStoreFlow.value).isEqualTo(accountsResponse)
|
||||
|
||||
coVerifyOrder {
|
||||
convertersContainer.getWalletAccountsResponseCF.create(userWallet)
|
||||
converter.convert(accountList)
|
||||
walletAccountsSaver.pushAndStore(userWalletId, accountsResponse)
|
||||
convertersContainer.createCryptoPortfolioConverter(userWalletId)
|
||||
converter.convertListBack(accounts)
|
||||
walletAccountsSaver.push(userWalletId, accountsResponse.accounts)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveAccounts if API request is failed`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accounts = accountList.accounts.filterIsInstance<CryptoPortfolio>()
|
||||
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
|
||||
val accountsResponse = mockk<GetWalletAccountsResponse>()
|
||||
val accountsResponse = createGetWalletAccountsResponse(userWalletId)
|
||||
accountsResponseStoreFlow.value = accountsResponse
|
||||
|
||||
val converter = mockk<GetWalletAccountsResponseConverter> {
|
||||
every { this@mockk.convert(accountList) } returns accountsResponse
|
||||
val converter = mockk<CryptoPortfolioConverter> {
|
||||
every { this@mockk.convertListBack(accounts) } returns accountsResponse.accounts
|
||||
}
|
||||
|
||||
every {
|
||||
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet)
|
||||
} returns converter
|
||||
every { convertersContainer.createCryptoPortfolioConverter(userWalletId) } returns converter
|
||||
|
||||
val exception = Exception("Test error")
|
||||
|
||||
coEvery { walletAccountsSaver.pushAndStore(userWalletId, accountsResponse) } throws exception
|
||||
coEvery { walletAccountsSaver.push(userWalletId, accountsResponse.accounts) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = runCatching { repository.saveAccounts(accountList) }.exceptionOrNull()!!
|
||||
|
|
@ -642,9 +636,8 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
Truth.assertThat(actual).hasMessageThat().isEqualTo(exception.message)
|
||||
|
||||
coVerifyOrder {
|
||||
convertersContainer.getWalletAccountsResponseCF.create(userWallet)
|
||||
converter.convert(accountList)
|
||||
walletAccountsSaver.pushAndStore(userWalletId, accountsResponse)
|
||||
convertersContainer.createCryptoPortfolioConverter(userWalletId)
|
||||
converter.convertListBack(accounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
package com.tangem.data.account.utils
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.account.converter.createWalletAccountDTO
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultWalletAccountsResponseFactoryTest {
|
||||
|
||||
private val userWalletsListRepository = mockk<UserWalletsListRepository>()
|
||||
private val cryptoPortfolioCF = mockk<CryptoPortfolioConverter.Factory>()
|
||||
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
|
||||
private val userTokensResponseFactory = mockk<UserTokensResponseFactory>()
|
||||
private val cardCryptoCurrencyFactory = mockk<CardCryptoCurrencyFactory>()
|
||||
|
||||
private val factory = DefaultWalletAccountsResponseFactory(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
cryptoPortfolioCF = cryptoPortfolioCF,
|
||||
userTokensResponseFactory = userTokensResponseFactory,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
||||
@BeforeEach
|
||||
fun setUpEach() {
|
||||
every { cryptoPortfolioCF.create(any()) } returns cryptoPortfolioConverter
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(
|
||||
userWalletsListRepository,
|
||||
cryptoPortfolioCF,
|
||||
cryptoPortfolioConverter,
|
||||
userTokensResponseFactory,
|
||||
cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns empty accounts when user wallet not found`() = runTest {
|
||||
// Arrange
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList()
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns userTokensResponse
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId = userWalletId, userTokensResponse = null)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
totalAccounts = 0,
|
||||
),
|
||||
accounts = emptyList(),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWalletsSync()
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns response with default tokens when userTokensResponse is null`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
|
||||
val defaultCoins = listOf(mockk<CryptoCurrency.Coin>())
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns defaultCoins
|
||||
|
||||
val defaultResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = listOf(mockk(relaxed = true)),
|
||||
)
|
||||
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = defaultCoins,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns defaultResponse
|
||||
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val accountsDTO = createWalletAccountDTO(userWalletId)
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO)
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId, null)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = defaultResponse.group,
|
||||
sort = defaultResponse.sort,
|
||||
totalAccounts = 1,
|
||||
),
|
||||
accounts = listOf(accountsDTO),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWalletsSync()
|
||||
cryptoPortfolioConverter.convertListBack(accounts)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet)
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = defaultCoins,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns response with default tokens when userTokensResponse is null and no default coins`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList()
|
||||
val defaultResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns defaultResponse
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns emptyList()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId, null)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = defaultResponse.group,
|
||||
sort = defaultResponse.sort,
|
||||
totalAccounts = 0,
|
||||
),
|
||||
accounts = emptyList(),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns response with assigned tokens`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
val assignedTokens = listOf(mockk<CryptoCurrency.Token>(), mockk<CryptoCurrency.Token>())
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = listOf(mockk(relaxed = true)),
|
||||
)
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = assignedTokens,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns userTokensResponse
|
||||
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
val accountsDTO = createWalletAccountDTO(userWalletId)
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO)
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId, userTokensResponse)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = userTokensResponse.group,
|
||||
sort = userTokensResponse.sort,
|
||||
totalAccounts = 1,
|
||||
),
|
||||
accounts = listOf(accountsDTO),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,18 +12,14 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
*/
|
||||
interface WalletAccountsSaver {
|
||||
|
||||
/** Push and store wallet accounts [response] by [userWalletId] */
|
||||
@Throws
|
||||
suspend fun pushAndStore(userWalletId: UserWalletId, response: GetWalletAccountsResponse)
|
||||
|
||||
/** Store wallet accounts [response] by [userWalletId] */
|
||||
suspend fun store(userWalletId: UserWalletId, response: GetWalletAccountsResponse)
|
||||
|
||||
/** Push wallet accounts [body] by [userWalletId] */
|
||||
@Throws
|
||||
suspend fun push(userWalletId: UserWalletId, body: SaveWalletAccountsResponse)
|
||||
suspend fun push(userWalletId: UserWalletId, body: SaveWalletAccountsResponse): GetWalletAccountsResponse?
|
||||
|
||||
/** Push wallet accounts [accounts] by [userWalletId] */
|
||||
@Throws
|
||||
suspend fun push(userWalletId: UserWalletId, accounts: List<WalletAccountDTO>)
|
||||
suspend fun push(userWalletId: UserWalletId, accounts: List<WalletAccountDTO>): GetWalletAccountsResponse?
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.Chain
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
|
|
@ -73,6 +74,17 @@ class CryptoCurrencyFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun createCoin(chainId: Int, extraDerivationPath: String?, userWallet: UserWallet): CryptoCurrency.Coin? {
|
||||
val blockchain: Blockchain? = Chain.entries.find { it.id == chainId }?.blockchain
|
||||
|
||||
return if (blockchain != null) {
|
||||
createCoin(blockchain, extraDerivationPath, userWallet)
|
||||
} else {
|
||||
Timber.e("Unable to get blockchain from chainId == $chainId")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun createCoin(
|
||||
blockchain: Blockchain,
|
||||
extraDerivationPath: String?,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,10 @@ private fun getCurrencyIdBody(network: Network): CurrencyIdBody {
|
|||
rawId = network.rawId,
|
||||
derivationPath = path.value,
|
||||
)
|
||||
is Network.DerivationPath.Card,
|
||||
is Network.DerivationPath.Card -> CurrencyIdBody.NetworkIdWithDerivationPath(
|
||||
rawId = network.rawId,
|
||||
derivationPath = path.value,
|
||||
)
|
||||
is Network.DerivationPath.None,
|
||||
-> CurrencyIdBody.NetworkId(network.rawId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
class UserTokensResponseFactory @Inject constructor() {
|
||||
|
||||
fun createUserTokensResponse(
|
||||
currencies: List<CryptoCurrency>,
|
||||
isGroupedByNetwork: Boolean,
|
||||
isSortedByBalance: Boolean,
|
||||
accountId: AccountId? = null,
|
||||
): UserTokensResponse {
|
||||
return UserTokensResponse(
|
||||
tokens = currencies.map(::createResponseToken),
|
||||
tokens = currencies.map { createResponseToken(currency = it, accountId = accountId) },
|
||||
group = if (isGroupedByNetwork) {
|
||||
UserTokensResponse.GroupType.NETWORK
|
||||
} else {
|
||||
|
|
@ -26,10 +29,11 @@ class UserTokensResponseFactory @Inject constructor() {
|
|||
)
|
||||
}
|
||||
|
||||
fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token {
|
||||
fun createResponseToken(currency: CryptoCurrency, accountId: AccountId? = null): UserTokensResponse.Token {
|
||||
return with(currency) {
|
||||
UserTokensResponse.Token(
|
||||
id = id.rawCurrencyId?.value,
|
||||
accountId = accountId?.value,
|
||||
networkId = network.backendId,
|
||||
derivationPath = network.derivationPath.value,
|
||||
name = name,
|
||||
|
|
|
|||
|
|
@ -326,6 +326,8 @@ class NetworkFactory @Inject constructor(
|
|||
Blockchain.Pepecoin, Blockchain.PepecoinTestnet,
|
||||
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet,
|
||||
Blockchain.Quai, Blockchain.QuaiTestnet,
|
||||
Blockchain.Linea, Blockchain.LineaTestnet,
|
||||
Blockchain.ArbitrumNova,
|
||||
-> Network.TransactionExtrasType.NONE
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ dependencies {
|
|||
|
||||
// region Project - Libs
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(tangemDeps.blockchain) { exclude(module = "joda-time") }
|
||||
// endregion
|
||||
|
||||
// region DI
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId.Companion.CONTRACT_ADDRESS_DELIMITER
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Suffix as CurrencyIdSuffix
|
||||
|
||||
/**
|
||||
* Converts between [CurrencyId] and [CryptoCurrency.ID].
|
||||
*
|
||||
* @property rawNetworkId the raw network ID associated with the currency
|
||||
* @property derivationPath the derivation path used for the network
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class CurrencyIdConverter(
|
||||
private val rawNetworkId: String,
|
||||
private val derivationPath: Network.DerivationPath,
|
||||
) : TwoWayConverter<CurrencyId, CryptoCurrency.ID> {
|
||||
|
||||
override fun convert(value: CurrencyId): CryptoCurrency.ID {
|
||||
val suffixParts = value.value.split(CONTRACT_ADDRESS_DELIMITER)
|
||||
|
||||
val rawId = suffixParts.getOrNull(0)
|
||||
val contractAddress = suffixParts.getOrNull(1)
|
||||
|
||||
return if (contractAddress.isNullOrBlank()) {
|
||||
getCoinId(
|
||||
coinId = rawId.takeUnless { it.isNullOrBlank() }
|
||||
?: error("Coin id is null for $rawNetworkId with $derivationPath"),
|
||||
)
|
||||
} else {
|
||||
getTokenId(
|
||||
rawTokenId = rawId?.ifBlank { null },
|
||||
contractAddress = contractAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: CryptoCurrency.ID): CurrencyId {
|
||||
return if (value.isCoin) {
|
||||
CurrencyId.createCoinId(
|
||||
coinId = Blockchain.fromId(value.rawNetworkId).toCoinId(),
|
||||
)
|
||||
} else {
|
||||
CurrencyId.createTokenId(
|
||||
rawTokenId = value.rawCurrencyId?.value,
|
||||
contractAddress = requireNotNull(value.contractAddress) {
|
||||
"Token contractAddress is null for token id: $this"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCoinId(coinId: String): CryptoCurrency.ID {
|
||||
return CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX,
|
||||
body = getCurrencyIdBody(),
|
||||
suffix = CurrencyIdSuffix.RawID(rawId = coinId),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getTokenId(rawTokenId: String?, contractAddress: String): CryptoCurrency.ID {
|
||||
val suffix = if (rawTokenId == null) {
|
||||
CurrencyIdSuffix.ContractAddress(contractAddress)
|
||||
} else {
|
||||
CurrencyIdSuffix.RawID(rawTokenId, contractAddress)
|
||||
}
|
||||
|
||||
return CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = getCurrencyIdBody(),
|
||||
suffix = suffix,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCurrencyIdBody(): CryptoCurrency.ID.Body {
|
||||
return when (derivationPath) {
|
||||
is Network.DerivationPath.Card -> {
|
||||
CryptoCurrency.ID.Body.NetworkIdWithDerivationPath(
|
||||
rawId = rawNetworkId,
|
||||
derivationPath = derivationPath.value,
|
||||
)
|
||||
}
|
||||
is Network.DerivationPath.Custom -> {
|
||||
CryptoCurrency.ID.Body.NetworkIdWithDerivationPath(
|
||||
rawId = rawNetworkId,
|
||||
derivationPath = derivationPath.value,
|
||||
)
|
||||
}
|
||||
is Network.DerivationPath.None -> CryptoCurrency.ID.Body.NetworkId(rawNetworkId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,46 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyAmount
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.utils.extensions.mapNotNullValues
|
||||
import java.math.BigDecimal
|
||||
|
||||
private typealias AmountsDataModel = Map<String, BigDecimal>
|
||||
private typealias AmountsDataModel = List<CurrencyAmount>
|
||||
private typealias AmountsDomainModel = Map<CryptoCurrency.ID, NetworkStatus.Amount>
|
||||
|
||||
/**
|
||||
* Converter from [AmountsDataModel] to [AmountsDomainModel] and vice versa
|
||||
*
|
||||
* @param rawNetworkId the raw network ID associated with the currency
|
||||
* @param derivationPath the derivation path used for the network
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object NetworkAmountsConverter : TwoWayConverter<AmountsDataModel, AmountsDomainModel> {
|
||||
internal class NetworkAmountsConverter(
|
||||
rawNetworkId: String,
|
||||
derivationPath: Network.DerivationPath,
|
||||
) : TwoWayConverter<AmountsDataModel, AmountsDomainModel> {
|
||||
|
||||
private val currencyIdConverter = CurrencyIdConverter(rawNetworkId, derivationPath)
|
||||
|
||||
override fun convert(value: AmountsDataModel): AmountsDomainModel {
|
||||
return value
|
||||
.mapKeys { CryptoCurrency.ID.fromValue(value = it.key) }
|
||||
.mapValues { (_, amount) -> NetworkStatus.Amount.Loaded(value = amount) }
|
||||
return value.associate {
|
||||
val currencyId = currencyIdConverter.convert(value = it.id)
|
||||
val amount = NetworkStatus.Amount.Loaded(value = it.amount)
|
||||
|
||||
currencyId to amount
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: AmountsDomainModel): AmountsDataModel {
|
||||
return value
|
||||
.mapKeys { (id, _) -> id.value }
|
||||
.mapNotNullValues { (_, amount) ->
|
||||
when (amount) {
|
||||
is NetworkStatus.Amount.Loaded -> amount.value
|
||||
is NetworkStatus.Amount.NotFound -> null
|
||||
}
|
||||
}
|
||||
return value.mapNotNull {
|
||||
val amount = it.value as? NetworkStatus.Amount.Loaded ?: return@mapNotNull null
|
||||
|
||||
CurrencyAmount(
|
||||
id = currencyIdConverter.convertBack(value = it.key),
|
||||
amount = amount.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,14 +15,22 @@ internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, Netwo
|
|||
return when (val status = value.value) {
|
||||
is NetworkStatus.Verified -> {
|
||||
val address = NetworkAddressConverter.convertBack(value = status.address)
|
||||
val amountsConverter = NetworkAmountsConverter(
|
||||
rawNetworkId = value.network.rawId,
|
||||
derivationPath = value.network.derivationPath,
|
||||
)
|
||||
val yieldSupplyStatusConverter = NetworkYieldSupplyStatusConverter(
|
||||
rawNetworkId = value.network.rawId,
|
||||
derivationPath = value.network.derivationPath,
|
||||
)
|
||||
|
||||
NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(value = value.network.rawId),
|
||||
derivationPath = NetworkDerivationPathConverter.convertBack(value = value.network.derivationPath),
|
||||
selectedAddress = address.selectedAddress,
|
||||
availableAddresses = address.addresses,
|
||||
amounts = NetworkAmountsConverter.convertBack(value = status.amounts),
|
||||
yieldSupplyStatuses = NetworkYieldSupplyStatusConverter.convertBack(status.yieldSupplyStatuses),
|
||||
amounts = amountsConverter.convertBack(value = status.amounts),
|
||||
yieldSupplyStatuses = yieldSupplyStatusConverter.convertBack(status.yieldSupplyStatuses),
|
||||
)
|
||||
}
|
||||
is NetworkStatus.NoAccount -> {
|
||||
|
|
|
|||
|
|
@ -2,45 +2,43 @@ package com.tangem.data.networks.converters
|
|||
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.utils.extensions.mapNotNullValues
|
||||
|
||||
private typealias YieldSupplyStatusDataModel = Map<String, NetworkStatusDM.YieldSupplyStatus?>
|
||||
private typealias YieldSupplyStatusDataModel = List<NetworkStatusDM.YieldSupplyStatus>
|
||||
private typealias YieldSupplyStatusDomainModel = Map<CryptoCurrency.ID, YieldSupplyStatus?>
|
||||
|
||||
internal object NetworkYieldSupplyStatusConverter :
|
||||
TwoWayConverter<YieldSupplyStatusDataModel, YieldSupplyStatusDomainModel> {
|
||||
internal class NetworkYieldSupplyStatusConverter(
|
||||
rawNetworkId: String,
|
||||
derivationPath: Network.DerivationPath,
|
||||
) : TwoWayConverter<YieldSupplyStatusDataModel, YieldSupplyStatusDomainModel> {
|
||||
|
||||
private val currencyIdConverter = CurrencyIdConverter(rawNetworkId, derivationPath)
|
||||
|
||||
override fun convert(value: YieldSupplyStatusDataModel): YieldSupplyStatusDomainModel {
|
||||
return value
|
||||
.mapKeys { CryptoCurrency.ID.fromValue(value = it.key) }
|
||||
.mapValues { (_, yieldSupplyStatus) ->
|
||||
if (yieldSupplyStatus != null) {
|
||||
YieldSupplyStatus(
|
||||
isActive = yieldSupplyStatus.isActive,
|
||||
isInitialized = yieldSupplyStatus.isInitialized,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
return value.associate {
|
||||
val id = currencyIdConverter.convert(value = it.id)
|
||||
val status = YieldSupplyStatus(
|
||||
isActive = it.isActive,
|
||||
isInitialized = it.isInitialized,
|
||||
isAllowedToSpend = it.isAllowedToSpend,
|
||||
)
|
||||
|
||||
id to status
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: YieldSupplyStatusDomainModel): YieldSupplyStatusDataModel {
|
||||
return value
|
||||
.mapKeys { (id, _) -> id.value }
|
||||
.mapNotNullValues { (_, yieldSupplyStatus) ->
|
||||
if (yieldSupplyStatus != null) {
|
||||
NetworkStatusDM.YieldSupplyStatus(
|
||||
isActive = yieldSupplyStatus.isActive,
|
||||
isInitialized = yieldSupplyStatus.isInitialized,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
return value.mapNotNull { (currencyId, yieldSupplyStatus) ->
|
||||
if (yieldSupplyStatus == null) return@mapNotNull null
|
||||
|
||||
NetworkStatusDM.YieldSupplyStatus(
|
||||
id = currencyIdConverter.convertBack(value = currencyId),
|
||||
isActive = yieldSupplyStatus.isActive,
|
||||
isInitialized = yieldSupplyStatus.isInitialized,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,14 +22,26 @@ internal object SimpleNetworkStatusConverter : Converter<NetworkStatusDM, Simple
|
|||
),
|
||||
)
|
||||
|
||||
val derivationPath = NetworkDerivationPathConverter.convert(value = value.derivationPath)
|
||||
|
||||
val amountsConverter = NetworkAmountsConverter(
|
||||
rawNetworkId = value.networkId.value,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
||||
val yieldSupplyStatusConverter = NetworkYieldSupplyStatusConverter(
|
||||
rawNetworkId = value.networkId.value,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
||||
val status = when (value) {
|
||||
is NetworkStatusDM.Verified -> {
|
||||
NetworkStatus.Verified(
|
||||
address = address,
|
||||
amounts = NetworkAmountsConverter.convert(value = value.amounts),
|
||||
amounts = amountsConverter.convert(value = value.amounts),
|
||||
pendingTransactions = emptyMap(),
|
||||
source = StatusSource.CACHE,
|
||||
yieldSupplyStatuses = NetworkYieldSupplyStatusConverter.convert(value = value.yieldSupplyStatuses),
|
||||
yieldSupplyStatuses = yieldSupplyStatusConverter.convert(value = value.yieldSupplyStatuses),
|
||||
)
|
||||
}
|
||||
is NetworkStatusDM.NoAccount -> {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
|||
import com.tangem.data.networks.repository.DefaultNetworksRepository
|
||||
import com.tangem.data.networks.store.DefaultNetworksStatusesStore
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.utils.DefaultNetworksCleaner
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
|
|
@ -15,6 +16,7 @@ import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
|||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||
import com.tangem.datasource.utils.setTypes
|
||||
import com.tangem.domain.networks.repository.NetworksRepository
|
||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -38,6 +40,7 @@ internal object NetworkDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
): NetworksStatusesStore {
|
||||
return DefaultNetworksStatusesStore(
|
||||
context = context,
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
persistenceDataStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
|
|
@ -45,7 +48,7 @@ internal object NetworkDataModule {
|
|||
types = mapWithStringKeyTypes(valueTypes = setTypes<NetworkStatusDM>()),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "networks_statuses") },
|
||||
produceFile = { context.dataStoreFile(fileName = "networks_statuses_2") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
dispatchers = dispatchers,
|
||||
|
|
@ -67,4 +70,18 @@ internal object NetworkDataModule {
|
|||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworksCleaner(
|
||||
networksStatusesStore: NetworksStatusesStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): NetworksCleaner {
|
||||
return DefaultNetworksCleaner(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.data.networks.converters.NetworkStatusDataModelConverter
|
||||
import com.tangem.data.networks.converters.SimpleNetworkStatusConverter
|
||||
|
|
@ -20,6 +21,7 @@ import kotlinx.coroutines.flow.firstOrNull
|
|||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
internal typealias WalletIdWithSimpleStatus = Map<String, Set<SimpleNetworkStatus>>
|
||||
internal typealias WalletIdWithStatusDM = Map<String, Set<NetworkStatusDM>>
|
||||
|
|
@ -27,11 +29,13 @@ internal typealias WalletIdWithStatusDM = Map<String, Set<NetworkStatusDM>>
|
|||
/**
|
||||
* Default implementation of [NetworksStatusesStore]
|
||||
*
|
||||
* @param context context
|
||||
* @property runtimeStore runtime store
|
||||
* @property persistenceDataStore persistence store
|
||||
* @param dispatchers dispatchers
|
||||
*/
|
||||
internal class DefaultNetworksStatusesStore(
|
||||
context: Context,
|
||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithSimpleStatus>,
|
||||
private val persistenceDataStore: DataStore<WalletIdWithStatusDM>,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -41,6 +45,16 @@ internal class DefaultNetworksStatusesStore(
|
|||
|
||||
init {
|
||||
scope.launch {
|
||||
try {
|
||||
val oldFile = File(context.filesDir, "datastore/networks_statuses")
|
||||
|
||||
if (oldFile.exists()) {
|
||||
oldFile.delete()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error while deleting old networks statuses datastore file")
|
||||
}
|
||||
|
||||
val cachedStatuses = persistenceDataStore.data.firstOrNull() ?: return@launch
|
||||
|
||||
runtimeStore.store(
|
||||
|
|
@ -96,6 +110,20 @@ internal class DefaultNetworksStatusesStore(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
persistenceDataStore.updateData { storedStatuses ->
|
||||
storedStatuses.toMutableMap().apply {
|
||||
val updatedValues = this[userWalletId.stringValue].orEmpty().filterNot {
|
||||
networks.any { network ->
|
||||
it.networkId.value == network.rawId && it.derivationPath.value == network.derivationPath.value
|
||||
}
|
||||
}
|
||||
|
||||
this[userWalletId.stringValue] = updatedValues.toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateInRuntime(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
|
|
|
|||
|
|
@ -41,4 +41,7 @@ internal interface NetworksStatusesStore {
|
|||
* See complex methods in `NetworksStatusesStoreExt`.
|
||||
*/
|
||||
suspend fun store(userWalletId: UserWalletId, status: NetworkStatus)
|
||||
|
||||
/** Clear statuses of [networks] by [userWalletId] */
|
||||
suspend fun clear(userWalletId: UserWalletId, networks: Set<Network>)
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.data.networks.utils
|
||||
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Default implementation of [NetworksCleaner].
|
||||
*
|
||||
* @property networksStatusesStore Store to manage network statuses.
|
||||
* @property walletManagersFacade Facade to manage wallet managers.
|
||||
* @property dispatchers Coroutine dispatchers provider.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultNetworksCleaner(
|
||||
private val networksStatusesStore: NetworksStatusesStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NetworksCleaner {
|
||||
|
||||
override suspend fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
withContext(dispatchers.default) {
|
||||
val (networks, tokens) = currencies.partitionByType()
|
||||
|
||||
coroutineScope {
|
||||
launch { cleanStore(userWalletId = userWalletId, networks = networks) }
|
||||
launch { cleanWalletManager(userWalletId = userWalletId, networks = networks, tokens = tokens) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanStore(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
if (networks.isNotEmpty()) {
|
||||
networksStatusesStore.clear(userWalletId = userWalletId, networks = networks)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanWalletManager(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
tokens: Set<CryptoCurrency.Token>,
|
||||
) {
|
||||
if (networks.isNotEmpty()) {
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = networks)
|
||||
}
|
||||
|
||||
if (tokens.isNotEmpty()) {
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrency>.partitionByType(): Pair<Set<Network>, Set<CryptoCurrency.Token>> {
|
||||
val networks = mutableSetOf<Network>()
|
||||
val tokens = mutableSetOf<CryptoCurrency.Token>()
|
||||
|
||||
for (currency in this) {
|
||||
when (currency) {
|
||||
is CryptoCurrency.Coin -> networks.add(currency.network)
|
||||
is CryptoCurrency.Token -> tokens.add(currency)
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(networks, tokens)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CurrencyIdConverterTest {
|
||||
|
||||
private val rawNetworkId = "ETH"
|
||||
private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0")
|
||||
private val derivationPathHashCode = "-1843072795"
|
||||
private val converter = CurrencyIdConverter(rawNetworkId = rawNetworkId, derivationPath = derivationPath)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Convert {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun convert(model: ConvertModel) {
|
||||
// Act
|
||||
val actual = runCatching { converter.convert(value = model.value) }
|
||||
|
||||
// Assert
|
||||
actual
|
||||
.onSuccess {
|
||||
Truth.assertThat(it).isEqualTo(model.expected.getOrNull())
|
||||
}
|
||||
.onFailure {
|
||||
val expected = model.expected.exceptionOrNull()!!
|
||||
|
||||
Truth.assertThat(it).isInstanceOf(expected::class.java)
|
||||
Truth.assertThat(it).hasMessageThat().isEqualTo(expected.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels(): Collection<ConvertModel> = listOf(
|
||||
// create coin id
|
||||
ConvertModel(
|
||||
value = CurrencyId.createCoinId("ethereum"),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ETH→$derivationPathHashCode⟩ethereum"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createCoinId(""),
|
||||
expected = Result.failure(
|
||||
IllegalStateException("Coin id is null for $rawNetworkId with $derivationPath"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createCoinId(" "),
|
||||
expected = Result.failure(
|
||||
IllegalStateException("Coin id is null for $rawNetworkId with $derivationPath"),
|
||||
),
|
||||
),
|
||||
// create token id
|
||||
ConvertModel(
|
||||
value = CurrencyId.createTokenId(
|
||||
rawTokenId = "usdt",
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createTokenId(
|
||||
rawTokenId = null,
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createTokenId(
|
||||
rawTokenId = "",
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createTokenId(
|
||||
rawTokenId = " ",
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createTokenId(
|
||||
rawTokenId = "usdt",
|
||||
contractAddress = "",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ETH→$derivationPathHashCode⟩usdt"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createTokenId(
|
||||
rawTokenId = "usdt",
|
||||
contractAddress = " ",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ETH→$derivationPathHashCode⟩usdt"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ConvertBack {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun convertBack(model: ConvertBackModel) {
|
||||
// Act
|
||||
val actual = runCatching { converter.convertBack(value = model.value) }
|
||||
|
||||
// Assert
|
||||
actual
|
||||
.onSuccess {
|
||||
Truth.assertThat(it).isEqualTo(model.expected.getOrNull())
|
||||
}
|
||||
.onFailure {
|
||||
val expected = model.expected.exceptionOrNull()!!
|
||||
|
||||
Truth.assertThat(it).isInstanceOf(expected::class.java)
|
||||
Truth.assertThat(it).hasMessageThat().isEqualTo(expected.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels(): Collection<ConvertBackModel> = listOf(
|
||||
ConvertBackModel(
|
||||
value = CryptoCurrency.ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum"),
|
||||
expected = Result.success(
|
||||
CurrencyId.createCoinId("ethereum"),
|
||||
),
|
||||
),
|
||||
ConvertBackModel(
|
||||
value = CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CurrencyId.createTokenId(
|
||||
rawTokenId = "usdt",
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertBackModel(
|
||||
value = CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CurrencyId.createTokenId(
|
||||
rawTokenId = null,
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class ConvertModel(val value: CurrencyId, val expected: Result<CryptoCurrency.ID>)
|
||||
|
||||
data class ConvertBackModel(val value: CryptoCurrency.ID, val expected: Result<CurrencyId>)
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class NetworkAddressConverterTest {
|
||||
|
||||
@Nested
|
||||
|
|
@ -18,7 +19,7 @@ internal class NetworkAddressConverterTest {
|
|||
inner class Convert {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
@ProvideTestModels
|
||||
fun convert(model: ConvertModel) {
|
||||
// Act
|
||||
val actual = runCatching { NetworkAddressConverter.convert(value = model.value) }
|
||||
|
|
@ -190,7 +191,7 @@ internal class NetworkAddressConverterTest {
|
|||
inner class ConvertBack {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
@ProvideTestModels
|
||||
fun convertBack(model: ConvertBackModel) {
|
||||
// Act
|
||||
val actual = NetworkAddressConverter.convertBack(value = model.value)
|
||||
|
|
|
|||
|
|
@ -1,77 +1,99 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Body
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Prefix
|
||||
import com.tangem.domain.models.network.NetworkStatus.Amount
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkStatus.Amount.Loaded
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class NetworkAmountsConverterTest {
|
||||
|
||||
private val rawNetworkId = "ETH"
|
||||
private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0")
|
||||
private val derivationPathHashCode = "-1843072795"
|
||||
private val converter = NetworkAmountsConverter(rawNetworkId = rawNetworkId, derivationPath = derivationPath)
|
||||
|
||||
@Test
|
||||
fun convert() {
|
||||
// Arrange
|
||||
val value = mapOf(
|
||||
"coin⟨BCH⟩bitcoin-cash" to BigDecimal.ZERO,
|
||||
"coin⟨ETH→12367123⟩ethereum" to BigDecimal.ONE,
|
||||
val value = listOf(
|
||||
NetworkStatusDM.CurrencyAmount(CurrencyId.createCoinId(coinId = "ethereum"), BigDecimal.ONE),
|
||||
NetworkStatusDM.CurrencyAmount(
|
||||
id = CurrencyId.createTokenId(
|
||||
rawTokenId = "usdt",
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
amount = BigDecimal.ZERO,
|
||||
),
|
||||
NetworkStatusDM.CurrencyAmount(
|
||||
id = CurrencyId.createTokenId(
|
||||
rawTokenId = null,
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = NetworkAmountsConverter.convert(value)
|
||||
val actual = converter.convert(value)
|
||||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BCH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "bitcoin-cash"),
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.ZERO),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.TEN),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun convertBack() {
|
||||
// Arrange
|
||||
val value = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BCH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "bitcoin-cash"),
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.ZERO),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to Loaded(value = BigDecimal.ONE),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BTC"),
|
||||
suffix = ID.Suffix.RawID(rawId = "bitcoin"),
|
||||
) to Amount.NotFound,
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.TEN),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = NetworkAmountsConverter.convertBack(value)
|
||||
val actual = converter.convertBack(value)
|
||||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
"coin⟨BCH⟩bitcoin-cash" to BigDecimal.ZERO,
|
||||
"coin⟨ETH→12367123⟩ethereum" to BigDecimal.ONE,
|
||||
val expected = listOf(
|
||||
NetworkStatusDM.CurrencyAmount(CurrencyId.createCoinId(coinId = "ethereum"), BigDecimal.ONE),
|
||||
NetworkStatusDM.CurrencyAmount(
|
||||
id = CurrencyId.createTokenId(
|
||||
rawTokenId = "usdt",
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
amount = BigDecimal.ZERO,
|
||||
),
|
||||
NetworkStatusDM.CurrencyAmount(
|
||||
id = CurrencyId.createTokenId(
|
||||
rawTokenId = null,
|
||||
contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
amount = BigDecimal.TEN,
|
||||
),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.data.networks.converters
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.*
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Body
|
||||
|
|
@ -14,7 +16,6 @@ import com.tangem.domain.models.network.NetworkStatus.Amount
|
|||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -26,7 +27,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
private val network: Network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
@ProvideTestModels
|
||||
fun convert(model: ConvertModel) {
|
||||
// Act
|
||||
val actual = NetworkStatusDataModelConverter.convert(value = model.value)
|
||||
|
|
@ -48,11 +49,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
),
|
||||
),
|
||||
amounts = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BCH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "bitcoin-cash"),
|
||||
) to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue(value = "coin⟨ETH→0⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BTC"),
|
||||
|
|
@ -61,11 +58,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
),
|
||||
pendingTransactions = mapOf(), // doesn't matter
|
||||
yieldSupplyStatuses = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BCH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "bitcoin-cash"),
|
||||
) to YieldSupplyStatus(
|
||||
ID.fromValue(value = "token⟨ETH→0⟩usdt⚓0x1") to YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
|
|
@ -79,22 +72,25 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
source = StatusSource.ACTUAL, // doesn't matter
|
||||
),
|
||||
),
|
||||
expected = NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
expected = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "",
|
||||
type = NetworkStatusDM.DerivationPath.Type.NONE,
|
||||
type = DerivationPath.Type.NONE,
|
||||
),
|
||||
selectedAddress = "0x123",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x123",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amounts = mapOf("coin⟨BCH⟩bitcoin-cash" to BigDecimal.ZERO),
|
||||
yieldSupplyStatuses = mapOf(
|
||||
"coin⟨BCH⟩bitcoin-cash" to NetworkStatusDM.YieldSupplyStatus(
|
||||
amounts = listOf(
|
||||
CurrencyAmount(CurrencyId.createCoinId("ethereum"), BigDecimal.ZERO),
|
||||
),
|
||||
yieldSupplyStatuses = listOf(
|
||||
YieldSupplyStatus(
|
||||
id = CurrencyId.createTokenId("usdt", "0x1"),
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
|
|
@ -120,17 +116,17 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
source = StatusSource.ACTUAL, // doesn't matter
|
||||
),
|
||||
),
|
||||
expected = NetworkStatusDM.NoAccount(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
expected = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "",
|
||||
type = NetworkStatusDM.DerivationPath.Type.NONE,
|
||||
type = DerivationPath.Type.NONE,
|
||||
),
|
||||
selectedAddress = "0x123",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x123",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amountToCreateAccount = BigDecimal.ONE,
|
||||
|
|
|
|||
|
|
@ -2,82 +2,74 @@ package com.tangem.data.networks.converters
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Body
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Prefix
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class NetworkYieldSupplyStatusConverterTest {
|
||||
|
||||
private val rawNetworkId = "ETH"
|
||||
private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0")
|
||||
private val derivationPathHashCode = "-1843072795"
|
||||
private val converter = NetworkYieldSupplyStatusConverter(rawNetworkId, derivationPath)
|
||||
|
||||
private val domainStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun convert() {
|
||||
// Arrange
|
||||
val value = mapOf(
|
||||
"coin⟨ETH⟩ethereum" to NetworkStatusDM.YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
"coin⟨ETH→12367123⟩ethereum" to null,
|
||||
val value = listOf(
|
||||
createDataStatus(id = CurrencyId.createCoinId("ethereum")),
|
||||
createDataStatus(id = CurrencyId.createTokenId("usdt", "0x1")),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = NetworkYieldSupplyStatusConverter.convert(value)
|
||||
val actual = converter.convert(value)
|
||||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "ETH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to null,
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to domainStatus,
|
||||
ID.fromValue("token⟨ETH→$derivationPathHashCode⟩usdt⚓0x1") to domainStatus,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun convertBack() {
|
||||
// Arrange
|
||||
val value = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "ETH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to null,
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to domainStatus,
|
||||
ID.fromValue("token⟨ETH→$derivationPathHashCode⟩usdt⚓0x1") to domainStatus,
|
||||
ID.fromValue("token⟨ETH→$derivationPathHashCode⟩usdc⚓0x1") to null,
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = NetworkYieldSupplyStatusConverter.convertBack(value)
|
||||
val actual = converter.convertBack(value)
|
||||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
"coin⟨ETH⟩ethereum" to NetworkStatusDM.YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
val expected = listOf(
|
||||
createDataStatus(id = CurrencyId.createCoinId("ethereum")),
|
||||
createDataStatus(id = CurrencyId.createTokenId("usdt", "0x1")),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||
}
|
||||
|
||||
private fun createDataStatus(id: CurrencyId): NetworkStatusDM.YieldSupplyStatus {
|
||||
return NetworkStatusDM.YieldSupplyStatus(
|
||||
id = id,
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@ package com.tangem.data.networks.converters
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.*
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Body
|
||||
import com.tangem.domain.models.currency.CryptoCurrency.ID.Prefix
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
|
|
@ -15,7 +15,6 @@ import com.tangem.domain.models.network.NetworkStatus.Amount
|
|||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -27,7 +26,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
private val network: Network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
@ProvideTestModels
|
||||
fun convert(model: ConvertModel) {
|
||||
// Act
|
||||
val actual = runCatching { SimpleNetworkStatusConverter.convert(value = model.value) }
|
||||
|
|
@ -48,34 +47,28 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
private fun provideTestModels() = listOf(
|
||||
// region Verified
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "0x1",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
value = "0x1",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
),
|
||||
NetworkStatusDM.Address(
|
||||
value = "0x2",
|
||||
type = NetworkStatusDM.Address.Type.Secondary,
|
||||
),
|
||||
Address(value = "0x1", type = Address.Type.Primary),
|
||||
Address(value = "0x2", type = Address.Type.Secondary),
|
||||
),
|
||||
amounts = mapOf(
|
||||
"coin⟨BCH⟩bitcoin-cash" to BigDecimal.ZERO,
|
||||
"coin⟨ETH→12367123⟩ethereum" to BigDecimal.ONE,
|
||||
amounts = listOf(
|
||||
CurrencyAmount(CurrencyId.createCoinId("ethereum"), BigDecimal.ZERO),
|
||||
CurrencyAmount(CurrencyId.createTokenId("usdt", "0x1"), BigDecimal.ZERO),
|
||||
),
|
||||
yieldSupplyStatuses = mapOf(
|
||||
"coin⟨ETH⟩ethereum" to NetworkStatusDM.YieldSupplyStatus(
|
||||
yieldSupplyStatuses = listOf(
|
||||
YieldSupplyStatus(
|
||||
id = CurrencyId.createCoinId("ethereum"),
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
"coin⟨ETH⟩ethereum" to null,
|
||||
),
|
||||
),
|
||||
expected = SimpleNetworkStatus(
|
||||
|
|
@ -101,33 +94,16 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
),
|
||||
amounts = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BCH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "bitcoin-cash"),
|
||||
) to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkIdWithDerivationPath(rawId = "ETH", derivationPathHashCode = 12367123),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to Amount.Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue("coin⟨ETH→3046160⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue("token⟨ETH→3046160⟩usdt⚓0x1") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
),
|
||||
pendingTransactions = emptyMap(),
|
||||
yieldSupplyStatuses = mapOf(
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "ETH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to YieldSupplyStatus(
|
||||
ID.fromValue("coin⟨ETH→3046160⟩ethereum") to YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "ETH"),
|
||||
suffix = ID.Suffix.RawID(rawId = "ethereum"),
|
||||
) to null,
|
||||
),
|
||||
source = StatusSource.CACHE,
|
||||
),
|
||||
|
|
@ -137,21 +113,21 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
|
||||
// region NoAccount
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.NoAccount(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "0x1",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x1",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x2",
|
||||
type = NetworkStatusDM.Address.Type.Secondary,
|
||||
type = Address.Type.Secondary,
|
||||
),
|
||||
),
|
||||
amountToCreateAccount = BigDecimal.ONE,
|
||||
|
|
@ -189,83 +165,83 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
|
||||
// region Error
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "0x1",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x2",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amounts = emptyMap(),
|
||||
yieldSupplyStatuses = emptyMap(),
|
||||
amounts = emptyList(),
|
||||
yieldSupplyStatuses = emptyList(),
|
||||
),
|
||||
expected = Result.failure(
|
||||
exception = IllegalArgumentException("Selected address must not be null"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "0x1",
|
||||
availableAddresses = setOf(),
|
||||
amounts = emptyMap(),
|
||||
yieldSupplyStatuses = emptyMap(),
|
||||
amounts = emptyList(),
|
||||
yieldSupplyStatuses = emptyList(),
|
||||
),
|
||||
expected = Result.failure(
|
||||
exception = IllegalArgumentException("Selected address must not be null"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x1",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x2",
|
||||
type = NetworkStatusDM.Address.Type.Secondary,
|
||||
type = Address.Type.Secondary,
|
||||
),
|
||||
),
|
||||
amounts = emptyMap(),
|
||||
yieldSupplyStatuses = emptyMap(),
|
||||
amounts = emptyList(),
|
||||
yieldSupplyStatuses = emptyList(),
|
||||
),
|
||||
expected = Result.failure(
|
||||
exception = IllegalArgumentException("Selected address must not be null"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.NoAccount(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x1",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x2",
|
||||
type = NetworkStatusDM.Address.Type.Secondary,
|
||||
type = Address.Type.Secondary,
|
||||
),
|
||||
),
|
||||
amountToCreateAccount = BigDecimal.ONE,
|
||||
|
|
@ -276,17 +252,17 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.NoAccount(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "0x1",
|
||||
availableAddresses = setOf(
|
||||
NetworkStatusDM.Address(
|
||||
Address(
|
||||
value = "0x2",
|
||||
type = NetworkStatusDM.Address.Type.Primary,
|
||||
type = Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amountToCreateAccount = BigDecimal.ONE,
|
||||
|
|
@ -297,11 +273,11 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = NetworkStatusDM.NoAccount(
|
||||
networkId = NetworkStatusDM.ID(network.rawId),
|
||||
derivationPath = NetworkStatusDM.DerivationPath(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = NetworkStatusDM.DerivationPath.Type.CARD,
|
||||
type = DerivationPath.Type.CARD,
|
||||
),
|
||||
selectedAddress = "0x1",
|
||||
availableAddresses = setOf(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.networks.toSimple
|
|||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ internal class GetTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal class InitializationTest {
|
|||
every { persistenceStore.data } returns emptyFlow()
|
||||
|
||||
DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
@ -44,6 +45,7 @@ internal class InitializationTest {
|
|||
val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
@ -66,6 +68,7 @@ internal class InitializationTest {
|
|||
}
|
||||
|
||||
DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -27,6 +28,7 @@ internal class ParameterizedStoreStatusTest(private val model: Model) {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -27,6 +28,7 @@ internal class ParameterizedStoreSuccessTest(private val model: Model) {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -26,6 +27,7 @@ internal class ParameterizedStoreTest(private val model: Model) {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,6 +25,7 @@ internal class SetSourceAsCacheTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,6 +25,7 @@ internal class SetSourceAsOnlyCacheTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
|||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,6 +25,7 @@ internal class StoreStatusTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
|||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -23,6 +24,7 @@ internal class StoreSuccessTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
|||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -23,6 +24,7 @@ internal class StoreTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.models.network.NetworkAddress
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -27,6 +28,7 @@ internal class UpdateStatusSourceTest {
|
|||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStore(
|
||||
context = mockk(),
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.data.networks.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultNetworksCleanerTest {
|
||||
|
||||
private val networksStatusesStore = mockk<NetworksStatusesStore>(relaxed = true)
|
||||
private val walletManagersFacade = mockk<WalletManagersFacade>(relaxed = true)
|
||||
private val cleaner = DefaultNetworksCleaner(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
private val network = cryptoCurrencyFactory.ethereum.network
|
||||
private val coin = cryptoCurrencyFactory.ethereum
|
||||
private val token = cryptoCurrencyFactory.createToken(Blockchain.Ethereum)
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
clearMocks(networksStatusesStore, walletManagersFacade)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should clear networks and remove managers and tokens when called`() = runTest {
|
||||
// Arrange
|
||||
val currencies = listOf(coin, token)
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.clear(userWalletId, setOf(network))
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle empty currencies`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = emptyList())
|
||||
|
||||
// Assert
|
||||
coVerifyOrder(inverse = true) {
|
||||
networksStatusesStore.clear(userWalletId = any(), networks = any())
|
||||
walletManagersFacade.remove(userWalletId = any(), networks = any())
|
||||
walletManagersFacade.removeTokens(userWalletId = any(), tokens = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should clear only networks when there are no tokens`() = runTest {
|
||||
val currencies = listOf(coin)
|
||||
|
||||
cleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.clear(userWalletId, setOf(network))
|
||||
walletManagersFacade.remove(userWalletId = userWalletId, networks = setOf(network))
|
||||
}
|
||||
|
||||
coVerifyOrder(inverse = true) {
|
||||
walletManagersFacade.removeTokens(userWalletId = any(), tokens = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should clear only tokens when there are no networks`() = runTest {
|
||||
// Arrange
|
||||
val currencies = listOf(token)
|
||||
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, currencies = currencies)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
walletManagersFacade.removeTokens(userWalletId = userWalletId, tokens = setOf(token))
|
||||
}
|
||||
|
||||
coVerifyOrder(inverse = true) {
|
||||
networksStatusesStore.clear(userWalletId = any(), networks = any())
|
||||
walletManagersFacade.remove(userWalletId = any(), networks = any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -160,5 +160,7 @@ public val Blockchain.mercuryoNetwork: String?
|
|||
Blockchain.Pepecoin, Blockchain.PepecoinTestnet -> null
|
||||
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet -> null
|
||||
Blockchain.Quai, Blockchain.QuaiTestnet -> null
|
||||
Blockchain.Linea, Blockchain.LineaTestnet -> null
|
||||
Blockchain.ArbitrumNova -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.GB_COUNTRY
|
||||
|
|
@ -91,11 +92,16 @@ internal class DefaultSettingsRepository(
|
|||
}
|
||||
|
||||
override suspend fun shouldSaveAccessCodes(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, default = false)
|
||||
return appPreferencesStore.getSyncOrNull(key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY)?.not()
|
||||
?: appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY,
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setShouldSaveAccessCodes(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, value = value)
|
||||
appPreferencesStore.store(key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY, value = value.not())
|
||||
}
|
||||
|
||||
override suspend fun incrementAppLaunchCounter() {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.data.staking.DefaultStakingTransactionHashRepository
|
|||
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles
|
||||
import com.tangem.data.staking.utils.DefaultStakingCleaner
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
|
|
@ -21,6 +22,7 @@ import com.tangem.domain.staking.repositories.StakingErrorResolver
|
|||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -102,4 +104,16 @@ internal object StakingDataModule {
|
|||
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): StakingFeatureToggles {
|
||||
return DefaultStakingFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingCleaner(
|
||||
yieldsBalancesStore: YieldsBalancesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingCleaner {
|
||||
return DefaultStakingCleaner(
|
||||
yieldsBalancesStore = yieldsBalancesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -96,6 +96,16 @@ internal class DefaultYieldsBalancesStore(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun clear(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
persistenceStore.updateData { current ->
|
||||
current.toMutableMap().apply {
|
||||
this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty()
|
||||
.filterNot { it.getStakingId() in stakingIds }
|
||||
.toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<YieldBalanceWrapperDTO>) {
|
||||
val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = values)
|
||||
.filterNotNull()
|
||||
|
|
|
|||
|
|
@ -33,4 +33,7 @@ interface YieldsBalancesStore {
|
|||
|
||||
/** Store error by [userWalletId] and [stakingIds] */
|
||||
suspend fun storeError(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
|
||||
|
||||
/** Clear balances of [stakingIds] by [userWalletId] */
|
||||
suspend fun clear(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.data.staking.utils
|
||||
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
/**
|
||||
* Default implementation of [StakingCleaner].
|
||||
*
|
||||
* @property yieldsBalancesStore Store to manage yields balances.
|
||||
* @property dispatchers Coroutine dispatchers provider.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultStakingCleaner(
|
||||
private val yieldsBalancesStore: YieldsBalancesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingCleaner {
|
||||
|
||||
override suspend fun invoke(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
if (stakingIds.isEmpty()) return
|
||||
|
||||
with(dispatchers.default) {
|
||||
yieldsBalancesStore.clear(userWalletId, stakingIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.data.staking.utils
|
||||
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultStakingCleanerTest {
|
||||
|
||||
private val yieldsBalancesStore = mockk<YieldsBalancesStore>(relaxed = true)
|
||||
private val cleaner = DefaultStakingCleaner(
|
||||
yieldsBalancesStore = yieldsBalancesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val stakingIds = setOf(
|
||||
StakingID(integrationId = StakingIntegrationID.Coin.Cardano.value, address = "0x1"),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
clearMocks(yieldsBalancesStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should clear yields balances when called`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
|
||||
// Assert
|
||||
coVerifyOrder {
|
||||
yieldsBalancesStore.clear(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle empty stakingIds`() = runTest {
|
||||
// Act
|
||||
cleaner(userWalletId = userWalletId, stakingIds = emptySet())
|
||||
|
||||
// Assert
|
||||
coVerifyOrder(inverse = true) {
|
||||
yieldsBalancesStore.clear(userWalletId = any(), stakingIds = any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
|
|||
import com.tangem.datasource.local.preferences.utils.getObjectList
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectListSync
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectMap
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -54,7 +55,7 @@ internal class DefaultSwapTransactionRepository(
|
|||
storeTransactionState(
|
||||
txId = transaction.txId,
|
||||
status = it,
|
||||
refundTokenCurrency = null,
|
||||
accountWithCurrency = null,
|
||||
)
|
||||
}
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
|
|
@ -177,7 +178,7 @@ internal class DefaultSwapTransactionRepository(
|
|||
override suspend fun storeTransactionState(
|
||||
txId: String,
|
||||
status: SwapStatusModel,
|
||||
refundTokenCurrency: CryptoCurrency?,
|
||||
accountWithCurrency: Pair<AccountId?, CryptoCurrency>?,
|
||||
) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val savedMap = mutablePreferences.getObjectMap<SwapStatusDTO>(
|
||||
|
|
@ -187,8 +188,8 @@ internal class DefaultSwapTransactionRepository(
|
|||
val updatesMap = savedMap.toMutableMap()
|
||||
updatesMap[txId] = savedStatusConverter.convert(
|
||||
status.copy(
|
||||
refundTokensResponse = refundTokenCurrency?.let {
|
||||
userTokensResponseFactory.createResponseToken(refundTokenCurrency)
|
||||
refundTokensResponse = accountWithCurrency?.let { (accountId, currency) ->
|
||||
userTokensResponseFactory.createResponseToken(currency, accountId)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ internal class SavedSwapTransactionListConverter(
|
|||
fromCryptoCurrencyId = value.fromCryptoCurrencyId,
|
||||
toCryptoCurrencyId = value.toCryptoCurrencyId,
|
||||
fromTokensResponse = userTokensResponseFactory.createResponseToken(
|
||||
value.fromCryptoCurrency,
|
||||
currency = value.fromCryptoCurrency,
|
||||
accountId = null,
|
||||
),
|
||||
toTokensResponse = userTokensResponseFactory.createResponseToken(
|
||||
value.toCryptoCurrency,
|
||||
currency = value.toCryptoCurrency,
|
||||
accountId = null,
|
||||
),
|
||||
transactions = savedSwapTransactionConverter.convertList(value.transactions),
|
||||
)
|
||||
|
|
@ -74,8 +76,11 @@ internal class SavedSwapTransactionListConverter(
|
|||
userWalletId = userWalletId.stringValue,
|
||||
fromCryptoCurrencyId = fromCryptoCurrency.id.value,
|
||||
toCryptoCurrencyId = toCryptoCurrency.id.value,
|
||||
fromTokensResponse = userTokensResponseFactory.createResponseToken(fromCryptoCurrency),
|
||||
toTokensResponse = userTokensResponseFactory.createResponseToken(toCryptoCurrency),
|
||||
fromTokensResponse = userTokensResponseFactory.createResponseToken(
|
||||
currency = fromCryptoCurrency,
|
||||
accountId = null,
|
||||
),
|
||||
toTokensResponse = userTokensResponseFactory.createResponseToken(currency = toCryptoCurrency, accountId = null),
|
||||
transactions = tokenTransactions,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.data.tokens.di
|
||||
|
||||
import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface MultiWalletCryptoCurrenciesProducerModule {
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindMultiWalletCryptoCurrenciesProducerFactory(
|
||||
impl: DefaultMultiWalletCryptoCurrenciesProducer.Factory,
|
||||
): MultiWalletCryptoCurrenciesProducer.Factory
|
||||
}
|
||||
|
|
@ -74,77 +74,18 @@ internal class DefaultCurrenciesRepository(
|
|||
userTokensSaver.storeAndPush(userWalletId, response)
|
||||
}
|
||||
|
||||
override suspend fun saveNewCurrenciesList(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
override suspend fun saveCurrenciesLocal(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
withContext(dispatchers.io) {
|
||||
val savedResponse = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform add currencies action." },
|
||||
)
|
||||
|
||||
val newCurrencies = populateCurrenciesWithMissedCoins(currencies)
|
||||
|
||||
val updatedResponse = savedResponse.copy(
|
||||
tokens = newCurrencies.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse,
|
||||
tokens = currencies.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
userWallet = userWalletsStore.getSyncStrict(key = userWalletId),
|
||||
userTokens = updatedResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun addCurrencies(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): List<CryptoCurrency> = withContext(dispatchers.io) {
|
||||
val savedCurrencies = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
|
||||
)
|
||||
|
||||
val currenciesToAdd = filterAlreadyAddedCurrencies(
|
||||
savedCurrencies = savedCurrencies.tokens,
|
||||
currenciesToAdd = populateCurrenciesWithMissedCoins(currencies = currencies),
|
||||
)
|
||||
|
||||
val updatedResponse = savedCurrencies.copy(
|
||||
tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse,
|
||||
)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
userWallet = userWalletsStore.getSyncStrict(key = userWalletId),
|
||||
userTokens = updatedResponse,
|
||||
)
|
||||
|
||||
currenciesToAdd
|
||||
}
|
||||
|
||||
override suspend fun saveNewCurrenciesListCache(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
withContext(dispatchers.io) {
|
||||
val savedResponse = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform add currencies action." },
|
||||
)
|
||||
|
||||
val newCurrencies = populateCurrenciesWithMissedCoins(currencies)
|
||||
|
||||
val updatedResponse = savedResponse.copy(
|
||||
tokens = newCurrencies.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
userTokensSaver.store(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse,
|
||||
)
|
||||
userTokensSaver.store(userWalletId = userWalletId, response = updatedResponse)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
userWallet = userWalletsStore.getSyncStrict(key = userWalletId),
|
||||
|
|
@ -551,6 +492,10 @@ internal class DefaultCurrenciesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override fun createCoinCurrency(network: Network): CryptoCurrency.Coin {
|
||||
return cryptoCurrencyFactory.createCoin(network = network)
|
||||
}
|
||||
|
||||
override fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token {
|
||||
return cryptoCurrencyFactory.createToken(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.data.pay
|
||||
|
||||
import arrow.core.Either
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.ReceiveAddressModel.NameService
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.pay.DataForReceive
|
||||
import com.tangem.domain.pay.DataForReceiveFactory
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TangemPay: TokenReceiveConfigFactory"
|
||||
|
||||
internal class DefaultDataForReceiveFactory @Inject constructor(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : DataForReceiveFactory {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
CryptoCurrencyFactory(excludedBlockchains)
|
||||
}
|
||||
private val errorConverter by lazy(mode = LazyThreadSafetyMode.NONE) { TangemPayErrorConverter(moshi) }
|
||||
|
||||
override fun getDataForReceive(depositAddress: String, chainId: Int): Either<UniversalError, DataForReceive> {
|
||||
return try {
|
||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking()
|
||||
|
||||
/**
|
||||
* Create [CryptoCurrency.Coin] only for F&F.
|
||||
* Later will use [CryptoCurrency.Token] when contractAddresses will be provided by BFF.
|
||||
*/
|
||||
val currency = cryptoCurrencyFactory.createCoin(
|
||||
chainId = chainId,
|
||||
extraDerivationPath = null,
|
||||
userWallet = wallet,
|
||||
) ?: error("Cannot create crypto currency from chainId $chainId")
|
||||
|
||||
val result = DataForReceive(
|
||||
currency = currency,
|
||||
walletId = wallet.walletId,
|
||||
receiveAddress = listOf(ReceiveAddressModel(nameService = NameService.Default, value = depositAddress)),
|
||||
)
|
||||
|
||||
Either.Right(result)
|
||||
} catch (exception: Exception) {
|
||||
when (exception) {
|
||||
is CancellationException -> {
|
||||
throw exception
|
||||
}
|
||||
else -> {
|
||||
Timber.tag(TAG).e(exception)
|
||||
Either.Left(errorConverter.convert(exception))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.data.pay.di
|
||||
|
||||
import com.tangem.data.pay.DefaultDataForReceiveFactory
|
||||
import com.tangem.data.pay.repository.DefaultCardDetailsRepository
|
||||
import com.tangem.data.pay.repository.DefaultKycRepository
|
||||
import com.tangem.data.pay.repository.DefaultTangemPayTxHistoryRepository
|
||||
import com.tangem.data.pay.repository.DefaultOnboardingRepository
|
||||
import com.tangem.domain.pay.DataForReceiveFactory
|
||||
import com.tangem.domain.pay.repository.CardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
|
|
@ -37,6 +39,10 @@ internal interface TangemPayDataModule {
|
|||
@Singleton
|
||||
fun bindCardDetailsRepository(repository: DefaultCardDetailsRepository): CardDetailsRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindDataForReceiveFactory(factory: DefaultDataForReceiveFactory): DataForReceiveFactory
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ internal class DefaultCardDetailsRepository @Inject constructor(
|
|||
return when (env) {
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.DEV_2,
|
||||
ApiEnvironment.DEV_3,
|
||||
ApiEnvironment.STAGE,
|
||||
ApiEnvironment.MOCK,
|
||||
-> visaLibLoader.getOrCreateConfig().rainRSAPublicKey.dev
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext
|
|||
import javax.inject.Inject
|
||||
|
||||
private const val VALID_STATUS = "valid"
|
||||
private const val APPROVED_KYC_STATUS = "APPROVED"
|
||||
private const val TAG = "TangemPay: OnboardingRepository"
|
||||
|
||||
internal class DefaultOnboardingRepository @Inject constructor(
|
||||
|
|
@ -47,13 +48,36 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
|
||||
override suspend fun getMainScreenCustomerInfo(): Either<UniversalError, MainScreenCustomerInfo> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.requestWithPersistedToken { authHeader ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.result
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress()
|
||||
|
||||
val orderStatus = getOrderStatus().getOrNull() ?: error("Order status is null")
|
||||
when (val orderId = tangemPayStorage.getOrderId(customerWalletAddress)) {
|
||||
// If order id wasn't saved -> get customer info
|
||||
null -> {
|
||||
MainScreenCustomerInfo(
|
||||
info = getCustomerInfoWithPersistedToken(),
|
||||
orderStatus = OrderStatus.UNKNOWN,
|
||||
)
|
||||
}
|
||||
// If order id was saved -> check its status
|
||||
else -> {
|
||||
val orderStatus = getOrderStatus(orderId)
|
||||
val customerInfo = when (orderStatus) {
|
||||
// Kyc is passed and user waits for order creation -> no need to get customer info
|
||||
OrderStatus.NEW,
|
||||
OrderStatus.PROCESSING,
|
||||
-> CustomerInfo(productInstance = null, isKycApproved = true, cardInfo = null)
|
||||
|
||||
MainScreenCustomerInfo(info = getCustomerInfo(result), orderStatus = orderStatus)
|
||||
// Order was created/cancelled -> clear order id and get customer info
|
||||
OrderStatus.UNKNOWN,
|
||||
OrderStatus.COMPLETED,
|
||||
OrderStatus.CANCELED,
|
||||
-> getCustomerInfoWithPersistedToken().also {
|
||||
tangemPayStorage.clearOrderId(customerWalletAddress)
|
||||
}
|
||||
}
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,33 +102,35 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
balance = balance.availableBalance,
|
||||
currencyCode = balance.currency,
|
||||
customerWalletAddress = paymentAccount.customerWalletAddress,
|
||||
depositAddress = response.depositAddress,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return CustomerInfo(
|
||||
productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
|
||||
kycStatus = response?.kyc?.status,
|
||||
isKycApproved = response?.kyc?.status == APPROVED_KYC_STATUS,
|
||||
cardInfo = cardInfo,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getOrderStatus(): Either<UniversalError, OrderStatus> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val walletAddress = requestHelper.getCustomerWalletAddress()
|
||||
val orderId: String = tangemPayStorage.getOrderId(walletAddress)
|
||||
?: return@runWithErrorLogs OrderStatus.NOT_ISSUED
|
||||
private suspend fun getOrderStatus(orderId: String): OrderStatus {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader, orderId)
|
||||
}.result ?: error("Order result is null")
|
||||
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader, orderId)
|
||||
}.result ?: error("Order result is null")
|
||||
|
||||
when (result.status) {
|
||||
OrderStatus.NEW.apiName -> OrderStatus.NEW
|
||||
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
|
||||
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
|
||||
else -> OrderStatus.CANCELED
|
||||
}
|
||||
return when (result.status) {
|
||||
OrderStatus.NEW.apiName -> OrderStatus.NEW
|
||||
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
|
||||
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
|
||||
else -> OrderStatus.CANCELED
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCustomerInfoWithPersistedToken(): CustomerInfo {
|
||||
val result = requestHelper.requestWithPersistedToken { authHeader ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.result
|
||||
return getCustomerInfo(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,6 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
}.result
|
||||
val items = TangemPayTxHistoryItemConverter.convertList(result.transactions).filterNotNull()
|
||||
txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items)
|
||||
}
|
||||
}.onLeft { error(it.toString()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,27 +5,19 @@ import com.squareup.moshi.Moshi
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
|
|
@ -37,9 +29,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemPayStorage: TangemPayStorage,
|
||||
private val authDataSource: TangemPayAuthDataSource,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val networkFactory: NetworkFactory,
|
||||
) {
|
||||
|
|
@ -49,7 +39,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
private val refreshTokensMutex = Mutex()
|
||||
private var refreshTokensJob: Deferred<VisaAuthTokens>? = null
|
||||
|
||||
private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi)
|
||||
private val errorConverter = TangemPayErrorConverter(moshi)
|
||||
|
||||
suspend fun <T : Any> runWithErrorLogs(tag: String, requestBlock: suspend () -> T): Either<UniversalError, T> {
|
||||
return try {
|
||||
|
|
@ -62,7 +52,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
}
|
||||
else -> {
|
||||
Timber.tag(tag).e(exception)
|
||||
Either.Left(mapError(exception))
|
||||
Either.Left(errorConverter.convert(exception))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,12 +76,6 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getWallets(): Flow<List<UserWallet>> = if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.userWallets.map { requireNotNull(it) }
|
||||
} else {
|
||||
userWalletsListManager.userWallets
|
||||
}
|
||||
|
||||
private suspend fun <T : Any> performRequest(
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
getTokens: (suspend () -> VisaAuthTokens),
|
||||
|
|
@ -143,11 +127,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun fetchAuthInputData(): AuthInputData {
|
||||
val userWallets = getWallets()
|
||||
.filter { it.isNotEmpty() }
|
||||
.first()
|
||||
val wallet = userWallets.find { it is UserWallet.Cold } as? UserWallet.Cold
|
||||
?: error("Cannot find cold user wallet")
|
||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPay()
|
||||
|
||||
val network = networkFactory.create(
|
||||
blockchain = Blockchain.Polygon,
|
||||
|
|
@ -179,21 +159,6 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
tangemPayStorage.storeAuthTokens(customerWalletAddress, tokens)
|
||||
return tokens
|
||||
}
|
||||
|
||||
private fun mapError(throwable: Throwable): UniversalError {
|
||||
return if (throwable is ApiResponseError.HttpException) {
|
||||
val errorBody = throwable.errorBody ?: return VisaApiError.UnknownWithoutCode
|
||||
return runCatching {
|
||||
visaErrorAdapter.fromJson(errorBody)?.error?.code ?: throwable.code.numericCode
|
||||
}.map {
|
||||
VisaApiError.fromBackendError(it)
|
||||
}.getOrElse {
|
||||
VisaApiError.UnknownWithoutCode
|
||||
}
|
||||
} else {
|
||||
VisaApiError.UnknownWithoutCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class AuthInputData(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class TangemPayErrorConverter(moshi: Moshi) : Converter<Throwable, UniversalError> {
|
||||
|
||||
private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi)
|
||||
|
||||
override fun convert(value: Throwable): UniversalError {
|
||||
return if (value is ApiResponseError.HttpException) {
|
||||
val errorBody = value.errorBody ?: return VisaApiError.UnknownWithoutCode
|
||||
return runCatching {
|
||||
visaErrorAdapter.fromJson(errorBody)?.error?.code ?: value.code.numericCode
|
||||
}.map {
|
||||
VisaApiError.fromBackendError(it)
|
||||
}.getOrElse {
|
||||
VisaApiError.UnknownWithoutCode
|
||||
}
|
||||
} else {
|
||||
VisaApiError.UnknownWithoutCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class TangemPayWalletsManager @Inject constructor(
|
||||
private val manager: UserWalletsListManager,
|
||||
private val repository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend fun getDefaultWalletForTangemPay(): UserWallet.Cold {
|
||||
val userWalletsFlow = if (useNewRepository()) repository.userWallets else manager.userWallets
|
||||
val userWallets = userWalletsFlow.filter { !it.isNullOrEmpty() }.first()
|
||||
return findColdWallet(userWallets)
|
||||
}
|
||||
|
||||
fun getDefaultWalletForTangemPayBlocking(): UserWallet.Cold {
|
||||
val userWallets = if (useNewRepository()) repository.userWallets.value else manager.userWalletsSync
|
||||
return findColdWallet(userWallets)
|
||||
}
|
||||
|
||||
private fun useNewRepository(): Boolean = hotWalletFeatureToggles.isHotWalletEnabled
|
||||
|
||||
private fun findColdWallet(userWallets: List<UserWallet>?): UserWallet.Cold {
|
||||
return userWallets?.find { it is UserWallet.Cold } as? UserWallet.Cold
|
||||
?: error("Cannot find cold user wallet")
|
||||
}
|
||||
}
|
||||
|
|
@ -169,6 +169,7 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
return when (env) {
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.DEV_2,
|
||||
ApiEnvironment.DEV_3,
|
||||
ApiEnvironment.STAGE,
|
||||
ApiEnvironment.MOCK,
|
||||
-> rsaPublicKey.dev
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal object TangemPayTxHistoryItemConverter :
|
|||
merchantName = spend.merchantName,
|
||||
enrichedMerchantCategory = spend.enrichedMerchantCategory,
|
||||
merchantCategory = spend.merchantCategory,
|
||||
status = spend.status,
|
||||
status = TangemPayTxHistoryItemStatusConverter.convert(spend.status),
|
||||
enrichedMerchantIconUrl = spend.enrichedMerchantIcon,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object TangemPayTxHistoryItemStatusConverter : Converter<String, TangemPayTxHistoryItem.Status> {
|
||||
override fun convert(value: String): TangemPayTxHistoryItem.Status {
|
||||
return when (value.uppercase()) {
|
||||
"PENDING" -> TangemPayTxHistoryItem.Status.PENDING
|
||||
"RESERVED" -> TangemPayTxHistoryItem.Status.RESERVED
|
||||
"COMPLETED" -> TangemPayTxHistoryItem.Status.COMPLETED
|
||||
"DECLINED" -> TangemPayTxHistoryItem.Status.DECLINED
|
||||
else -> TangemPayTxHistoryItem.Status.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ internal class VisaApiRequestMaker @Inject constructor(
|
|||
userWalletsStore.update(userWalletId) { userWallet ->
|
||||
userWallet.requireColdWallet().copy(
|
||||
scanResponse = userWallet.scanResponse.copy(
|
||||
visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired,
|
||||
// visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -65,9 +65,9 @@ internal class VisaApiRequestMaker @Inject constructor(
|
|||
userWalletsStore.update(userWalletId) { userWallet ->
|
||||
userWallet.requireColdWallet().copy(
|
||||
scanResponse = userWallet.scanResponse.copy(
|
||||
visaCardActivationStatus = VisaCardActivationStatus.Activated(
|
||||
visaAuthTokens = newTokens,
|
||||
),
|
||||
// visaCardActivationStatus = VisaCardActivationStatus.Activated(
|
||||
// visaAuthTokens = newTokens,
|
||||
// ),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -93,8 +93,9 @@ internal class VisaApiRequestMaker @Inject constructor(
|
|||
@Throws
|
||||
private fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens {
|
||||
val userWallet = findVisaUserWallet(userWalletId)
|
||||
val status = userWallet.requireColdWallet().scanResponse.visaCardActivationStatus
|
||||
?: error("Visa card activation status not found")
|
||||
// val status = userWallet.requireColdWallet().scanResponse.visaCardActivationStatus
|
||||
// ?: error("Visa card activation status not found")
|
||||
val status: VisaCardActivationStatus = TODO("Fix visaCardActivationStatus retrieval")
|
||||
|
||||
if (status is VisaCardActivationStatus.RefreshTokenExpired) {
|
||||
throw RefreshTokenExpiredException()
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
package com.tangem.data.yield.supply
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.data.yield.supply.converters.YieldMarketTokenConverter
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.data.yield.supply.converters.YieldTokenStatusConverter
|
||||
import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketTokenStatus
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.collections.map
|
||||
|
||||
internal class DefaultYieldSupplyMarketRepository(
|
||||
private val yieldSupplyApi: YieldSupplyApi,
|
||||
private val store: YieldMarketsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : YieldSupplyMarketRepository {
|
||||
|
||||
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = withContext(dispatchers.io) {
|
||||
store.getSyncOrNull()?.enrichNetworkIds()
|
||||
}
|
||||
|
||||
override suspend fun updateMarkets(): List<YieldMarketToken> = withContext(dispatchers.io) {
|
||||
val response = yieldSupplyApi.getYieldMarkets().getOrThrow()
|
||||
val domain = response.marketDtos.map(YieldMarketTokenConverter::convert)
|
||||
store.store(domain)
|
||||
domain
|
||||
}
|
||||
|
||||
override fun getMarketsFlow(): Flow<List<YieldMarketToken>> = store.get().map {
|
||||
it.enrichNetworkIds()
|
||||
}
|
||||
|
||||
override suspend fun getTokenStatus(cryptoCurrencyToken: CryptoCurrency.Token): YieldMarketTokenStatus {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
val response = yieldSupplyApi.getYieldTokenStatus(chainId, cryptoCurrencyToken.contractAddress).getOrThrow()
|
||||
return YieldTokenStatusConverter.convert(response)
|
||||
}
|
||||
|
||||
override suspend fun getTokenChart(cryptoCurrencyToken: CryptoCurrency.Token): YieldSupplyMarketChartData {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
val response = yieldSupplyApi.getYieldTokenChart(chainId, cryptoCurrencyToken.contractAddress).getOrThrow()
|
||||
return YieldTokenChartConverter.convert(response)
|
||||
}
|
||||
|
||||
private fun List<YieldMarketToken>.enrichNetworkIds(): List<YieldMarketToken> {
|
||||
val chainIdMap = Blockchain.entries.associate { it.getChainId() to it.toNetworkId() }
|
||||
return this.map { token ->
|
||||
token.copy(backendId = chainIdMap[token.chainId])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.data.yield.supply
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.yieldsupply.YieldSupplyProvider
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.data.yield.supply.converters.YieldMarketTokenConverter
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.collections.map
|
||||
|
||||
internal class DefaultYieldSupplyRepository(
|
||||
private val yieldSupplyApi: YieldSupplyApi,
|
||||
private val store: YieldMarketsStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : YieldSupplyRepository {
|
||||
|
||||
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = withContext(dispatchers.io) {
|
||||
val cache = store.getSyncOrNull().orEmpty()
|
||||
val domain = cache.map(YieldMarketTokenConverter::convert)
|
||||
domain.enrichNetworkIds()
|
||||
}
|
||||
|
||||
override suspend fun updateMarkets(): List<YieldMarketToken> = withContext(dispatchers.io) {
|
||||
val chains = Blockchain.yieldSupplySupportedBlockchains().map { it.getChainId() }.joinToString(",")
|
||||
val response = yieldSupplyApi.getYieldMarkets(chainId = chains).getOrThrow()
|
||||
val domain = response.marketDtos.map(YieldMarketTokenConverter::convert)
|
||||
store.store(response.marketDtos)
|
||||
domain
|
||||
}
|
||||
|
||||
override fun getMarketsFlow(): Flow<List<YieldMarketToken>> = store.get().map {
|
||||
it.map(YieldMarketTokenConverter::convert).enrichNetworkIds()
|
||||
}
|
||||
|
||||
override suspend fun getTokenStatus(cryptoCurrencyToken: CryptoCurrency.Token): YieldMarketToken {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
val response = yieldSupplyApi.getYieldTokenStatus(chainId, cryptoCurrencyToken.contractAddress).getOrThrow()
|
||||
return YieldMarketTokenConverter.convert(response)
|
||||
}
|
||||
|
||||
override suspend fun getTokenChart(cryptoCurrencyToken: CryptoCurrency.Token): YieldSupplyMarketChartData {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
val response = yieldSupplyApi.getYieldTokenChart(chainId, cryptoCurrencyToken.contractAddress).getOrThrow()
|
||||
return YieldTokenChartConverter.convert(response)
|
||||
}
|
||||
|
||||
override suspend fun isYieldSupplySupported(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = cryptoCurrency.network.toBlockchain(),
|
||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
||||
) ?: error("Wallet manager not found")
|
||||
|
||||
(walletManager as? YieldSupplyProvider)?.isSupported() ?: false
|
||||
}
|
||||
|
||||
override suspend fun activateProtocol(cryptoCurrencyToken: CryptoCurrency.Token): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
yieldSupplyApi.activateYieldModule(
|
||||
YieldSupplyChangeTokenStatusBody(
|
||||
tokenAddress = cryptoCurrencyToken.contractAddress,
|
||||
chainId = chainId,
|
||||
),
|
||||
).getOrThrow().isActive
|
||||
}
|
||||
|
||||
override suspend fun deactivateProtocol(cryptoCurrencyToken: CryptoCurrency.Token): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
yieldSupplyApi.deactivateYieldModule(
|
||||
YieldSupplyChangeTokenStatusBody(
|
||||
tokenAddress = cryptoCurrencyToken.contractAddress,
|
||||
chainId = chainId,
|
||||
),
|
||||
).getOrThrow().isActive
|
||||
}
|
||||
|
||||
private fun List<YieldMarketToken>.enrichNetworkIds(): List<YieldMarketToken> {
|
||||
val chainIdMap = Blockchain.entries.associate { it.getChainId() to it.toNetworkId() }
|
||||
return this.map { token ->
|
||||
token.copy(backendId = chainIdMap[token.chainId])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
package com.tangem.data.yield.supply.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldSupplyMarketTokenDto
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
||||
internal object YieldMarketTokenConverter : Converter<YieldMarketsResponse.MarketDto, YieldMarketToken> {
|
||||
override fun convert(value: YieldMarketsResponse.MarketDto): YieldMarketToken {
|
||||
internal object YieldMarketTokenConverter : Converter<YieldSupplyMarketTokenDto, YieldMarketToken> {
|
||||
override fun convert(value: YieldSupplyMarketTokenDto): YieldMarketToken {
|
||||
return YieldMarketToken(
|
||||
tokenAddress = value.tokenAddress.orEmpty(),
|
||||
apy = value.apy,
|
||||
isActive = value.isActive,
|
||||
apy = value.apy.orZero(),
|
||||
isActive = value.isActive ?: false,
|
||||
chainId = value.chainId ?: -1,
|
||||
maxFeeUSD = value.maxFeeUSD.orEmpty(),
|
||||
maxFeeNative = value.maxFeeNative.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.data.yield.supply.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldTokenStatusResponse
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketTokenStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object YieldTokenStatusConverter : Converter<YieldTokenStatusResponse, YieldMarketTokenStatus> {
|
||||
override fun convert(value: YieldTokenStatusResponse): YieldMarketTokenStatus {
|
||||
return YieldMarketTokenStatus(
|
||||
tokenAddress = value.tokenAddress.orEmpty(),
|
||||
tokenSymbol = value.tokenSymbol.orEmpty(),
|
||||
tokenName = value.tokenName.orEmpty(),
|
||||
apy = value.apy ?: SerializedBigDecimal.ZERO,
|
||||
isActive = value.isActive ?: false,
|
||||
chainId = value.chainId ?: -1,
|
||||
maxFeeUSD = value.maxFeeUSD.orEmpty(),
|
||||
maxFeeNative = value.maxFeeNative.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.data.yield.supply.di
|
||||
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyMarketRepository
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyRepository
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
||||
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -37,12 +37,14 @@ internal object YieldSupplyDataModule {
|
|||
fun provideYieldSupplyMarketRepository(
|
||||
yieldSupplyApi: YieldSupplyApi,
|
||||
store: YieldMarketsStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): YieldSupplyMarketRepository {
|
||||
return DefaultYieldSupplyMarketRepository(
|
||||
): YieldSupplyRepository {
|
||||
return DefaultYieldSupplyRepository(
|
||||
yieldSupplyApi = yieldSupplyApi,
|
||||
store = store,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue