Updated on 2026-08-14
This commit is contained in:
parent
e381fafeb8
commit
7cb4b57fe9
21 changed files with 556 additions and 230 deletions
|
|
@ -3,10 +3,13 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
import com.tangem.domain.account.usecase.*
|
||||
import com.tangem.feature.referral.data.ExternalReferralRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -43,9 +46,15 @@ internal object AccountDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideArchiveCryptoPortfolioUseCase(
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
referralRepository: ExternalReferralRepository,
|
||||
): ArchiveCryptoPortfolioUseCase {
|
||||
return ArchiveCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
return ArchiveCryptoPortfolioUseCase(
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
crudRepository = accountsCRUDRepository,
|
||||
referralRepository = referralRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.account.models
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
|
|
@ -40,4 +41,14 @@ data class AccountStatusList(
|
|||
fun flattenCurrencies(): List<CryptoCurrencyStatus> = accountStatuses
|
||||
.map { accountStatus -> accountStatus.flattenCurrencies() }
|
||||
.flatten()
|
||||
|
||||
fun toAccountList(): Either<AccountList.Error, AccountList> {
|
||||
return AccountList(
|
||||
userWalletId = userWalletId,
|
||||
accounts = accountStatuses.map(AccountStatus::account),
|
||||
totalAccounts = totalAccounts,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import arrow.core.None
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import arrow.core.toOption
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase.Error
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.test.mock.MockAccounts.createAccount
|
||||
import io.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 ArchiveCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = ArchiveCryptoPortfolioUseCase(crudRepository)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should archive existing crypto portfolio account`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(derivationIndex = 1)
|
||||
val accountList = (AccountList.empty(userWalletId) + account).getOrNull()!!
|
||||
val accountId = account.accountId
|
||||
|
||||
val updatedAccountList = (accountList - account).getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if getAccounts returns None`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex.Main,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if getAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex.Main,
|
||||
)
|
||||
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if account not found`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountNotFound(accountId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(derivationIndex = 1)
|
||||
val accountList = (AccountList.empty(userWalletId) + account).getOrNull()!!
|
||||
val accountId = account.accountId
|
||||
|
||||
val updatedAccountList = (accountList - account).getOrNull()!!
|
||||
|
||||
val exception = IllegalStateException("Save failed")
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId("011")
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
api(projects.domain.quotes)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.networks)
|
||||
api(projects.domain.referral)
|
||||
api(projects.domain.staking)
|
||||
api(projects.domain.tokens)
|
||||
api(projects.domain.wallets)
|
||||
|
|
@ -42,7 +43,7 @@ dependencies {
|
|||
// end
|
||||
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(tangemDeps.blockchain)
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(projects.test.core)
|
||||
testImplementation(projects.test.mock)
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ internal object AccountStatusUseCaseModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideManageCryptoCurrenciesUseCase(
|
||||
singleAccountListSupplier: SingleAccountListSupplier,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
|
|
@ -103,7 +103,7 @@ internal object AccountStatusUseCaseModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ManageCryptoCurrenciesUseCase {
|
||||
return ManageCryptoCurrenciesUseCase(
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
derivationsRepository = derivationsRepository,
|
||||
|
|
|
|||
|
|
@ -1,31 +1,48 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.referral.ReferralRepository
|
||||
|
||||
/**
|
||||
* Use case for archiving a crypto portfolio.
|
||||
* This class provides functionality to archive a specific account within a user's crypto portfolio.
|
||||
* It ensures that the account exists and meets the necessary requirements before performing the operation.
|
||||
*
|
||||
* @property singleAccountStatusListSupplier supplier to get the list of account statuses
|
||||
* @property crudRepository repository for performing CRUD operations on accounts
|
||||
* @property referralRepository repository for handling referral status checks
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ArchiveCryptoPortfolioUseCase(
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
private val referralRepository: ReferralRepository,
|
||||
) {
|
||||
|
||||
/** Archives the specified account by its [accountId] */
|
||||
suspend operator fun invoke(accountId: AccountId): Either<Error, Unit> = either {
|
||||
val accountList = getAccountList(userWalletId = accountId.userWalletId)
|
||||
val accountStatusList = getAccountStatusList(userWalletId = accountId.userWalletId)
|
||||
val accountList = accountStatusList.toAccountList().getOrElse {
|
||||
raise(Error.CriticalTechError.AccountListRequirementsNotMet(cause = it))
|
||||
}
|
||||
|
||||
ensure(accountList.accounts.any { it.accountId == accountId }) {
|
||||
Error.CriticalTechError.AccountNotFound(accountId = accountId)
|
||||
}
|
||||
|
||||
checkReferralStatus(accountStatusList = accountStatusList, accountId = accountId)
|
||||
|
||||
val archivingAccount = accountList.accounts
|
||||
.firstOrNull { it.accountId == accountId }
|
||||
|
|
@ -38,16 +55,48 @@ class ArchiveCryptoPortfolioUseCase(
|
|||
saveAccounts(updatedAccounts)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
||||
return catch(
|
||||
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
|
||||
private suspend fun Raise<Error>.checkReferralStatus(accountStatusList: AccountStatusList, accountId: AccountId) {
|
||||
val referralStatus = catch(
|
||||
block = { referralRepository.getReferralStatus(userWalletId = accountStatusList.userWalletId.stringValue) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
.getOrElse { raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId)) }
|
||||
|
||||
val referralToken = referralStatus.token
|
||||
val address = referralStatus.address
|
||||
|
||||
if (!referralStatus.isActive || referralToken == null || address == null) return
|
||||
|
||||
val account = accountStatusList.accountStatuses
|
||||
.asSequence()
|
||||
.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||
.firstOrNull { it.accountId == accountId }
|
||||
|
||||
ensureNotNull(account) {
|
||||
Error.CriticalTechError.AccountNotFound(accountId = accountId)
|
||||
}
|
||||
|
||||
val statuses = account.flattenCurrencies()
|
||||
|
||||
val hasNotReferralToken = statuses.none { status ->
|
||||
val currency = status.currency
|
||||
|
||||
currency.network.backendId == referralToken.networkId &&
|
||||
(currency as? CryptoCurrency.Token)?.contractAddress == referralToken.contractAddress &&
|
||||
status.value.networkAddress?.availableAddresses?.any { it.value == address } == true
|
||||
}
|
||||
|
||||
ensure(hasNotReferralToken) { Error.ActiveReferralStatus }
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountStatusList(userWalletId: UserWalletId): AccountStatusList {
|
||||
return singleAccountStatusListSupplier.getSyncOrNull(
|
||||
SingleAccountStatusListProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
?: raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId))
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
|
||||
catch(
|
||||
arrow.core.raise.catch(
|
||||
block = { crudRepository.saveAccounts(accountList) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
|
|
@ -58,9 +107,14 @@ class ArchiveCryptoPortfolioUseCase(
|
|||
*/
|
||||
sealed interface Error {
|
||||
|
||||
val tag: String
|
||||
get() = this.javaClass.simpleName
|
||||
|
||||
data object ActiveReferralStatus : Error
|
||||
|
||||
/** Error indicating that a data operation failed */
|
||||
data class DataOperationFailed(val cause: Throwable) : Error {
|
||||
override fun toString(): String = "$this: Data operation failed: ${cause.message ?: "Unknown error"}"
|
||||
override fun toString(): String = "$tag: Data operation failed: ${cause.message ?: "Unknown error"}"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -82,6 +82,7 @@ class GetAccountCurrencyByAddressUseCase(
|
|||
for (id in userWalletIds) {
|
||||
val networkStatus = multiNetworkStatusSupplier.getSyncOrNull(
|
||||
params = MultiNetworkStatusProducer.Params(userWalletId = id),
|
||||
timeMillis = 1000L,
|
||||
)
|
||||
?.firstOrNull { it.getAddress() == address }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,18 @@ package com.tangem.domain.account.status.usecase
|
|||
import arrow.core.Either
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.core.utils.eitherOn
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
|
|
@ -27,7 +29,7 @@ import timber.log.Timber
|
|||
/**
|
||||
* Use case for saving crypto currencies to a specific account.
|
||||
*
|
||||
* @property singleAccountListSupplier Supplier to get account details.
|
||||
* @property singleAccountStatusListSupplier Supplier for fetching the status of a single account.
|
||||
* @property accountsCRUDRepository Repository for performing CRUD operations on accounts.
|
||||
* @property currenciesRepository Repository for managing currencies.
|
||||
* @property derivationsRepository Repository for deriving public keys.
|
||||
|
|
@ -43,7 +45,7 @@ import timber.log.Timber
|
|||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class ManageCryptoCurrenciesUseCase(
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
|
|
@ -76,12 +78,13 @@ class ManageCryptoCurrenciesUseCase(
|
|||
|
||||
val userWalletId = accountId.userWalletId
|
||||
withContext(NonCancellable) {
|
||||
val account = getAccount(accountId = accountId)
|
||||
val accountStatus = getAccountStatus(accountId = accountId)
|
||||
|
||||
val modifiedCurrencyList = account.cryptoCurrencies.modify(add = add, remove = remove)
|
||||
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
||||
.modify(add = add, remove = remove)
|
||||
|
||||
saveAccount(
|
||||
account = account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
||||
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
||||
)
|
||||
|
||||
val isDerivingFailed = derivePublicKeys(
|
||||
|
|
@ -117,10 +120,10 @@ class ManageCryptoCurrenciesUseCase(
|
|||
val userWalletId = accountId.userWalletId
|
||||
|
||||
withContext(NonCancellable) {
|
||||
val account = getAccount(accountId = accountId)
|
||||
val accountStatus = getAccountStatus(accountId = accountId)
|
||||
|
||||
val foundToken = account.cryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
||||
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
||||
.firstOrNull {
|
||||
it.network.backendId == networkId &&
|
||||
!it.isCustom &&
|
||||
|
|
@ -131,9 +134,10 @@ class ManageCryptoCurrenciesUseCase(
|
|||
|
||||
val tokenToAdd = findToken(userWalletId, contractAddress, networkId)
|
||||
|
||||
val modifiedCurrencyList = account.cryptoCurrencies.modify(add = listOf(tokenToAdd))
|
||||
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
||||
.modify(add = listOf(tokenToAdd))
|
||||
|
||||
saveAccount(account = account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
||||
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
||||
|
||||
parallelUpdatingScope.launch {
|
||||
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
|
||||
|
|
@ -144,26 +148,31 @@ class ManageCryptoCurrenciesUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<Throwable>.getAccount(accountId: AccountId): Account.CryptoPortfolio {
|
||||
val accountList = singleAccountListSupplier.getSyncOrNull(
|
||||
params = SingleAccountListProducer.Params(userWalletId = accountId.userWalletId),
|
||||
private suspend fun Raise<Throwable>.getAccountStatus(accountId: AccountId): AccountStatus.CryptoPortfolio {
|
||||
val accountStatusList = singleAccountStatusListSupplier.getSyncOrNull(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId = accountId.userWalletId),
|
||||
) ?: raise(IllegalStateException("No accounts for wallet ${accountId.userWalletId}"))
|
||||
|
||||
return accountList.accounts.firstOrNull { it.accountId == accountId } as? Account.CryptoPortfolio
|
||||
return accountStatusList.accountStatuses
|
||||
.firstOrNull { it.accountId == accountId } as? AccountStatus.CryptoPortfolio
|
||||
?: raise(IllegalStateException("No account with id $accountId"))
|
||||
}
|
||||
|
||||
private fun Set<CryptoCurrency>.modify(
|
||||
private fun List<CryptoCurrencyStatus>.modify(
|
||||
add: List<CryptoCurrency>,
|
||||
remove: List<CryptoCurrency> = emptyList(),
|
||||
): ModifiedCurrencyList {
|
||||
val mutableCurrencies = this.toMutableList()
|
||||
val mutableCurrencies = this.map(CryptoCurrencyStatus::currency).toMutableList()
|
||||
val added = mutableListOf<CryptoCurrency>()
|
||||
val removed = mutableListOf<CryptoCurrency>()
|
||||
|
||||
val existingCurrenciesById = mutableCurrencies.associateBy(::TempID)
|
||||
val existingCurrenciesById = this.associateBy(::TempID)
|
||||
|
||||
add.groupByNetwork { !existingCurrenciesById.containsKey(it) }
|
||||
add.groupByNetwork { tempID ->
|
||||
val found = existingCurrenciesById[tempID] ?: return@groupByNetwork true
|
||||
|
||||
found.value is CryptoCurrencyStatus.MissedDerivation
|
||||
}
|
||||
.forEach { (network, currenciesById) ->
|
||||
val coinTempId = TempID(network)
|
||||
|
||||
|
|
@ -204,20 +213,6 @@ class ManageCryptoCurrenciesUseCase(
|
|||
return ModifiedCurrencyList(added = added, removed = removed, total = mutableCurrencies)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Throwable>.saveAccount(account: Account.CryptoPortfolio) {
|
||||
catch(
|
||||
block = { accountsCRUDRepository.saveAccount(account) },
|
||||
catch = ::raise,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun derivePublicKeys(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> = Either.catch {
|
||||
derivationsRepository.derivePublicKeys(userWalletId = userWalletId, currencies = currencies)
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrency>.groupByNetwork(
|
||||
valuePredicate: (TempID) -> Boolean,
|
||||
): LinkedHashMap<Network, MutableMap<TempID, CryptoCurrency>> {
|
||||
|
|
@ -230,13 +225,27 @@ class ManageCryptoCurrenciesUseCase(
|
|||
val id = TempID(currency)
|
||||
|
||||
if (valuePredicate(id)) {
|
||||
mutableMap.put(id, currency)
|
||||
mutableMap[id] = currency
|
||||
}
|
||||
}
|
||||
|
||||
return destination
|
||||
}
|
||||
|
||||
private suspend fun Raise<Throwable>.saveAccount(account: Account.CryptoPortfolio) {
|
||||
catch(
|
||||
block = { accountsCRUDRepository.saveAccount(account) },
|
||||
catch = ::raise,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun derivePublicKeys(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> = Either.catch {
|
||||
derivationsRepository.derivePublicKeys(userWalletId = userWalletId, currencies = currencies)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Throwable>.findToken(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: String,
|
||||
|
|
@ -307,6 +316,12 @@ class ManageCryptoCurrenciesUseCase(
|
|||
derivationPath = currency.network.derivationPath,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
|
||||
constructor(status: CryptoCurrencyStatus) : this(
|
||||
networkId = status.currency.network.backendId,
|
||||
derivationPath = status.currency.network.derivationPath,
|
||||
contractAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
private data class ModifiedCurrencyList(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,307 @@
|
|||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase.Error
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.referral.ReferralRepository
|
||||
import com.tangem.domain.referral.ReferralStatus
|
||||
import com.tangem.test.mock.MockAccounts.createAccountList
|
||||
import io.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 ArchiveCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk()
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val referralRepository: ReferralRepository = mockk(relaxUnitFun = true)
|
||||
|
||||
private val useCase = ArchiveCryptoPortfolioUseCase(
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
crudRepository = crudRepository,
|
||||
referralRepository = referralRepository,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(singleAccountStatusListSupplier, referralRepository, crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should archive existing crypto portfolio account`() = runTest {
|
||||
// Arrange
|
||||
val accountList = createAccountList(activeAccounts = 2)
|
||||
val archivingAccount = accountList.accounts.last()
|
||||
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||
|
||||
val referralStatus = ReferralStatus(isActive = false, token = null, address = null)
|
||||
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = archivingAccount.accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should archive if referral status is active but token is null`() = runTest {
|
||||
// Arrange
|
||||
val accountList = createAccountList(activeAccounts = 2)
|
||||
val archivingAccount = accountList.accounts.last()
|
||||
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||
|
||||
val referralStatus = ReferralStatus(isActive = true, token = null, address = null)
|
||||
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = archivingAccount.accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should archive if referral status is active but token is absent`() = runTest {
|
||||
// Arrange
|
||||
val accountList = createAccountList(activeAccounts = 2)
|
||||
val archivingAccount = accountList.accounts.last()
|
||||
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||
|
||||
val token = ReferralStatus.Token(
|
||||
networkId = "ethereum",
|
||||
contractAddress = "0x1",
|
||||
)
|
||||
val referralStatus = ReferralStatus(isActive = true, token = token, address = "0xABC")
|
||||
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = archivingAccount.accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns error if account contains referral token `() = runTest {
|
||||
// Arrange
|
||||
val token = ReferralStatus.Token(
|
||||
networkId = "ethereum",
|
||||
contractAddress = "0x1",
|
||||
)
|
||||
val defaultAddress = "0xABC"
|
||||
|
||||
val cryptoCurrency = mockk<CryptoCurrency.Token> {
|
||||
every { this@mockk.network.backendId } returns token.networkId
|
||||
every { this@mockk.contractAddress } returns token.contractAddress!!
|
||||
}
|
||||
|
||||
val networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = defaultAddress,
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
)
|
||||
|
||||
val cryptoCurrencyStatus = mockk<CryptoCurrencyStatus> {
|
||||
every { this@mockk.currency } returns cryptoCurrency
|
||||
every { this@mockk.value.networkAddress } returns networkAddress
|
||||
}
|
||||
|
||||
val accountList = createAccountList(activeAccounts = 2)
|
||||
val archivingAccount = accountList.accounts.last()
|
||||
val archivingAccountStatus = AccountStatus.CryptoPortfolio(
|
||||
account = archivingAccount as Account.CryptoPortfolio,
|
||||
tokenList = TokenList.Ungrouped(
|
||||
totalFiatBalance = TotalFiatBalance.Failed,
|
||||
sortedBy = TokensSortType.NONE,
|
||||
currencies = listOf(cryptoCurrencyStatus),
|
||||
),
|
||||
priceChangeLce = Unit.lceError(),
|
||||
)
|
||||
val accountListStatus = with(accountList.toStatus()) {
|
||||
copy(
|
||||
accountStatuses = accountStatuses.filter { it.accountId != archivingAccount.accountId } +
|
||||
archivingAccountStatus,
|
||||
)
|
||||
}
|
||||
|
||||
(accountList - archivingAccount).getOrNull()!!
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountListStatus
|
||||
|
||||
val referralStatus = ReferralStatus(isActive = true, token = token, address = defaultAddress)
|
||||
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = archivingAccount.accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.ActiveReferralStatus.left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if supplier returns null`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||
)
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns null
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { singleAccountStatusListSupplier.getSyncOrNull(params) }
|
||||
coVerify(inverse = true) {
|
||||
referralRepository.getReferralStatus(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if account not found`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex.Companion(1).getOrNull()!!,
|
||||
)
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||
|
||||
val referralStatus = ReferralStatus(isActive = false, token = null, address = null)
|
||||
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountNotFound(accountId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { singleAccountStatusListSupplier.getSyncOrNull(params) }
|
||||
|
||||
coVerify(inverse = true) {
|
||||
referralRepository.getReferralStatus(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
val accountList = createAccountList(activeAccounts = 2)
|
||||
val archivingAccount = accountList.accounts.last()
|
||||
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||
|
||||
val referralStatus = ReferralStatus(isActive = false, token = null, address = null)
|
||||
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||
|
||||
val exception = IllegalStateException("Save failed")
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = archivingAccount.accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun AccountList.toStatus(): AccountStatusList {
|
||||
return AccountStatusList(
|
||||
userWalletId = Companion.userWalletId,
|
||||
accountStatuses = accounts.map {
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = it as Account.CryptoPortfolio,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = Unit.lceError(),
|
||||
)
|
||||
},
|
||||
totalAccounts = totalAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId("011")
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
// Arrange
|
||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
} returns null
|
||||
|
||||
// Act
|
||||
|
|
@ -124,7 +124,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
coVerifySequence {
|
||||
accountsCRUDRepository.getUserWalletsSync()
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
// Arrange
|
||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
} returns emptySet()
|
||||
|
||||
// Act
|
||||
|
|
@ -144,7 +144,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
coVerifySequence {
|
||||
accountsCRUDRepository.getUserWalletsSync()
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
} returns setOf(networkStatus)
|
||||
|
||||
// Act
|
||||
|
|
@ -169,7 +169,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
coVerifySequence {
|
||||
accountsCRUDRepository.getUserWalletsSync()
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
} returns setOf(networkStatus)
|
||||
coEvery {
|
||||
singleAccountListSupplier.getSyncOrNull(
|
||||
|
|
@ -201,7 +201,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
coVerifySequence {
|
||||
accountsCRUDRepository.getUserWalletsSync()
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
singleAccountListSupplier.getSyncOrNull(
|
||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
|
|
@ -222,7 +222,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
} returns setOf(networkStatus)
|
||||
coEvery {
|
||||
singleAccountListSupplier.getSyncOrNull(
|
||||
|
|
@ -238,7 +238,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
coVerifySequence {
|
||||
accountsCRUDRepository.getUserWalletsSync()
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
singleAccountListSupplier.getSyncOrNull(
|
||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
|
|
@ -257,7 +257,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
} returns setOf(networkStatus)
|
||||
coEvery {
|
||||
singleAccountListSupplier.getSyncOrNull(
|
||||
|
|
@ -274,7 +274,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
|
||||
coVerifySequence {
|
||||
accountsCRUDRepository.getUserWalletsSync()
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||
singleAccountListSupplier.getSyncOrNull(
|
||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.core.flow
|
|||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
/**
|
||||
* [Flow] supplier
|
||||
|
|
@ -16,8 +17,14 @@ interface FlowSupplier<Params : Any, Data : Any> {
|
|||
/** Supply [Flow] by [params] */
|
||||
operator fun invoke(params: Params): Flow<Data>
|
||||
|
||||
/** Get first [Data] by [params] or null if [Flow] is empty */
|
||||
suspend fun getSyncOrNull(params: Params): Data? {
|
||||
return invoke(params).firstOrNull()
|
||||
/** Synchronously get first value or null from [Flow] within [timeMillis] */
|
||||
suspend fun getSyncOrNull(params: Params, timeMillis: Long? = null): Data? {
|
||||
val block = suspend { invoke(params).firstOrNull() }
|
||||
|
||||
return if (timeMillis == null) {
|
||||
block()
|
||||
} else {
|
||||
withTimeoutOrNull(timeMillis) { block() }
|
||||
}
|
||||
}
|
||||
}
|
||||
1
domain/referral/.gitignore
vendored
Normal file
1
domain/referral/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
9
domain/referral/build.gradle.kts
Normal file
9
domain/referral/build.gradle.kts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(deps.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.domain.referral
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Repository for handling referral-related operations.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ReferralRepository {
|
||||
|
||||
/** Retrieves the referral status for a given [userWalletId] */
|
||||
suspend fun getReferralStatus(userWalletId: String): ReferralStatus
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ReferralStatus(
|
||||
val isActive: Boolean,
|
||||
val token: Token?,
|
||||
val address: String?,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Token(
|
||||
val networkId: String,
|
||||
val contractAddress: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.ToastMessage
|
||||
import com.tangem.domain.account.producer.SingleAccountProducer
|
||||
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.supplier.SingleAccountSupplier
|
||||
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
|
|
@ -104,13 +104,14 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun failedArchiveDialog(error: ArchiveCryptoPortfolioUseCase.Error) {
|
||||
// todo account referral case
|
||||
val titleRes = when (error) {
|
||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountNotFound,
|
||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
|
||||
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
|
||||
-> R.string.common_something_went_wrong
|
||||
is ArchiveCryptoPortfolioUseCase.Error.ActiveReferralStatus,
|
||||
-> R.string.account_could_not_archive_referral_program_title
|
||||
}
|
||||
val messageRes = when (error) {
|
||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
||||
|
|
@ -118,6 +119,8 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
|
||||
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
|
||||
-> R.string.account_could_not_archive
|
||||
is ArchiveCryptoPortfolioUseCase.Error.ActiveReferralStatus,
|
||||
-> R.string.account_could_not_archive_referral_program_message
|
||||
}
|
||||
|
||||
val dialogMessage = DialogMessage(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.referral)
|
||||
implementation(projects.features.referral.domain)
|
||||
|
||||
/** Libs */
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:ReferralConverter.kt$ExpectedAwardsConverter${ ExpectedAward( paymentDate = it.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(), amount = "${it.amount} ${it.currency}", ) }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -70,10 +70,10 @@ private class ExpectedAwardsConverter : Converter<ReferralResponse.ExpectedAward
|
|||
override fun convert(value: ReferralResponse.ExpectedAwards): ExpectedAwards {
|
||||
return ExpectedAwards(
|
||||
numberOfWallets = value.numberOfWallets,
|
||||
expectedAwards = value.list.map {
|
||||
expectedAwards = value.list.map { awardItem ->
|
||||
ExpectedAward(
|
||||
paymentDate = it.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(),
|
||||
amount = "${it.amount} ${it.currency}",
|
||||
paymentDate = awardItem.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(),
|
||||
amount = "${awardItem.amount} ${awardItem.currency}",
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore
|
|||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.referral.ReferralStatus
|
||||
import com.tangem.feature.referral.converters.ReferralConverter
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
|
|
@ -21,13 +22,15 @@ import kotlinx.coroutines.withContext
|
|||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
typealias ExternalReferralRepository = com.tangem.domain.referral.ReferralRepository
|
||||
|
||||
internal class ReferralRepositoryImpl @Inject constructor(
|
||||
private val referralApi: TangemTechApi,
|
||||
private val referralConverter: ReferralConverter,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : ReferralRepository {
|
||||
) : ReferralRepository, ExternalReferralRepository {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
|
||||
|
|
@ -47,6 +50,21 @@ internal class ReferralRepositoryImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getReferralStatus(userWalletId: String): ReferralStatus {
|
||||
val data = getReferralData(userWalletId)
|
||||
|
||||
return ReferralStatus(
|
||||
isActive = data is ReferralData.ParticipantData,
|
||||
token = data.tokens.firstOrNull()?.let { tokenData ->
|
||||
ReferralStatus.Token(
|
||||
networkId = tokenData.networkId,
|
||||
contractAddress = tokenData.contractAddress,
|
||||
)
|
||||
},
|
||||
address = (data as? ReferralData.ParticipantData)?.referral?.address,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean {
|
||||
val storedReferralData = referralStatus[userWalletId.stringValue] ?: getReferralData(userWalletId.stringValue)
|
||||
return storedReferralData is ReferralData.ParticipantData
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.feature.referral.converters.ReferralConverter
|
||||
import com.tangem.feature.referral.data.ExternalReferralRepository
|
||||
import com.tangem.feature.referral.data.ReferralRepositoryImpl
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -34,4 +35,22 @@ class ReferralRepositoryModule {
|
|||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExternalReferralRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
referralConverter: ReferralConverter,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): ExternalReferralRepository {
|
||||
return ReferralRepositoryImpl(
|
||||
referralApi = tangemTechApi,
|
||||
referralConverter = referralConverter,
|
||||
coroutineDispatcher = coroutineDispatcherProvider,
|
||||
userWalletsStore = userWalletsStore,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -360,6 +360,7 @@ include(":domain:notifications")
|
|||
include(":domain:notifications:models")
|
||||
include(":domain:express")
|
||||
include(":domain:express:models")
|
||||
include(":domain:referral")
|
||||
include(":domain:swap")
|
||||
include(":domain:swap:models")
|
||||
include(":domain:wallet-manager")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue