Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-01 11:50:47 +03:00
parent 1edf995ab0
commit 2dadb60975
26 changed files with 1110 additions and 48 deletions

View file

@ -1,10 +1,14 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import com.tangem.domain.common.wallets.UserWalletDataCleaner
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.DeleteWalletError
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.launch
/**
* Use case for deleting user wallet
@ -15,6 +19,8 @@ import com.tangem.utils.logging.TangemLogger
*/
class DeleteWalletUseCase(
private val userWalletsListRepository: UserWalletsListRepository,
private val userWalletDataCleaners: Set<UserWalletDataCleaner>,
private val appCoroutineScope: AppCoroutineScope,
) {
/**
@ -25,10 +31,22 @@ class DeleteWalletUseCase(
* @return [Either] with [com.tangem.domain.common.wallets.error.DeleteWalletError] or [Boolean] which indicates that there are still saved wallets.
* */
suspend operator fun invoke(userWalletId: UserWalletId): Either<DeleteWalletError, Boolean> {
return userWalletsListRepository.delete(userWalletIds = listOf(userWalletId))
val userWalletIds = listOf(userWalletId)
return userWalletsListRepository.delete(userWalletIds = userWalletIds)
.onRight { clearWalletDataInBackground(userWalletIds) }
.map { userWalletsListRepository.selectedUserWallet.value != null }
.onLeft {
TangemLogger.e("Failed to delete wallet with id ${userWalletId.value}: $it")
TangemLogger.e("Failed to delete wallet with id ${userWalletId.stringValue}: $it")
}
}
private fun clearWalletDataInBackground(userWalletIds: List<UserWalletId>) {
if (userWalletDataCleaners.isEmpty()) return
appCoroutineScope.launch {
userWalletDataCleaners.forEach { cleaner ->
runSuspendCatching { cleaner.clear(userWalletIds) }
.onFailure { TangemLogger.e("Failed to clear data for wallets $userWalletIds", it) }
}
}
}
}

View file

@ -0,0 +1,128 @@
package com.tangem.domain.wallets.usecase
import arrow.core.left
import arrow.core.right
import com.google.common.truth.Truth.assertThat
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
import com.tangem.domain.common.wallets.UserWalletDataCleaner
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.DeleteWalletError
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.test.core.TestAppCoroutineScope
import io.mockk.Runs
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
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)
internal class DeleteWalletUseCaseTest {
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxed = true)
private val firstCleaner: UserWalletDataCleaner = mockk()
private val secondCleaner: UserWalletDataCleaner = mockk()
@BeforeEach
fun resetMocks() {
clearMocks(userWalletsListRepository, firstCleaner, secondCleaner)
coEvery { userWalletsListRepository.delete(any()) } returns Unit.right()
every { userWalletsListRepository.selectedUserWallet } returns MutableStateFlow<UserWallet?>(null)
coEvery { firstCleaner.clear(any()) } just Runs
coEvery { secondCleaner.clear(any()) } just Runs
}
@Test
fun `GIVEN delete succeeds WHEN invoke THEN cleanup runs in background after invoke returns`() = runTest {
// Arrange
val useCase = createUseCase()
// Act
useCase(WALLET_ID)
// Assert — cleanup is fire-and-forget: not run yet when invoke returns
coVerify(exactly = 0) { firstCleaner.clear(any()) }
coVerify(exactly = 0) { secondCleaner.clear(any()) }
// ...and completes for every wallet once the app scope is drained
advanceUntilIdle()
coVerify(exactly = 1) { firstCleaner.clear(listOf(WALLET_ID)) }
coVerify(exactly = 1) { secondCleaner.clear(listOf(WALLET_ID)) }
}
@Test
fun `GIVEN delete fails WHEN invoke THEN cleaners are not run and error returned`() = runTest {
// Arrange
coEvery { userWalletsListRepository.delete(any()) } returns DeleteWalletError.UnableToDelete.left()
val useCase = createUseCase()
// Act
val result = useCase(WALLET_ID)
advanceUntilIdle()
// Assert
assertThat(result).isEqualTo(DeleteWalletError.UnableToDelete.left())
coVerify(exactly = 0) { firstCleaner.clear(any()) }
coVerify(exactly = 0) { secondCleaner.clear(any()) }
}
@Test
fun `GIVEN a wallet still selected WHEN invoke THEN returns right true`() = runTest {
// Arrange
every { userWalletsListRepository.selectedUserWallet } returns MutableStateFlow(wallet)
val useCase = createUseCase()
// Act
val result = useCase(WALLET_ID)
// Assert
assertThat(result).isEqualTo(true.right())
}
@Test
fun `GIVEN no wallet selected WHEN invoke THEN returns right false`() = runTest {
// Arrange
val useCase = createUseCase()
// Act
val result = useCase(WALLET_ID)
// Assert
assertThat(result).isEqualTo(false.right())
}
@Test
fun `GIVEN one cleaner throws WHEN invoke THEN other cleaners still run and result is right`() = runTest {
// Arrange
coEvery { firstCleaner.clear(any()) } throws IllegalStateException("boom")
val useCase = createUseCase()
// Act
val result = useCase(WALLET_ID)
advanceUntilIdle()
// Assert
assertThat(result).isEqualTo(false.right())
coVerify(exactly = 1) { secondCleaner.clear(listOf(WALLET_ID)) }
}
private fun TestScope.createUseCase() = DeleteWalletUseCase(
userWalletsListRepository = userWalletsListRepository,
userWalletDataCleaners = setOf(firstCleaner, secondCleaner),
appCoroutineScope = TestAppCoroutineScope(this),
)
private companion object {
val WALLET_ID = UserWalletId("0011")
val wallet: UserWallet = MockUserWalletFactory.create()
}
}