From 2d0bc9d9e6cfe012512487694d53c43d0580bc82 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 24 Apr 2026 14:00:32 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/tap/di/data/CardSdkModule.kt | 24 +++++++ .../tangem/tap/di/domain/CardDomainModule.kt | 3 +- .../DefaultDeleteSavedAccessCodesUseCase.kt | 22 ------ .../DefaultUserWalletsListRepository.kt | 6 +- .../ui/resetcard/model/ResetCardModel.kt | 3 + domain/card/build.gradle.kts | 10 ++- .../card/DeleteSavedAccessCodesUseCase.kt | 29 +++++++- .../card/DeleteSavedAccessCodesUseCaseTest.kt | 69 +++++++++++++++++++ .../wallets/usecase/DeleteWalletUseCase.kt | 9 ++- gradle/dependencies.toml | 1 + libs/tangem-sdk-api/build.gradle.kts | 7 -- .../com/tangem/sdk/api/di/CardSdkModule.kt | 32 --------- 12 files changed, 141 insertions(+), 74 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt create mode 100644 domain/card/src/test/java/com/tangem/domain/card/DeleteSavedAccessCodesUseCaseTest.kt delete mode 100644 libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt diff --git a/app/src/main/java/com/tangem/tap/di/data/CardSdkModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardSdkModule.kt index 118bcb16a7..496271ef06 100644 --- a/app/src/main/java/com/tangem/tap/di/data/CardSdkModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/CardSdkModule.kt @@ -1,12 +1,18 @@ package com.tangem.tap.di.data +import android.content.Context import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.operations.attestation.CardArtworksProvider import com.tangem.tap.data.DefaultCardSdkProvider import dagger.Binds import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import java.io.File import javax.inject.Singleton @Module @@ -20,4 +26,22 @@ internal interface CardSdkModule { @Binds @Singleton fun providerCardSdkLifecycleObserver(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkOwner + + companion object { + + @Provides + @Singleton + fun provideCardArtworksProvider( + sdkRepository: CardSdkConfigRepository, + @ApplicationContext context: Context, + ): CardArtworksProvider { + return CardArtworksProvider( + tangemApiBaseUrlProvider = { sdkRepository.sdk.config.tangemApiBaseUrl }, + artworksDirectory = File( + context.getExternalFilesDir(null) ?: context.filesDir, + "card_artworks", + ).apply { mkdirs() }, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 7f99ec03db..45b884766c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -11,7 +11,6 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.usecase.* import com.tangem.sdk.api.TangemSdkManager -import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase import com.tangem.tap.domain.card.DefaultResetCardUseCase import dagger.Module import dagger.Provides @@ -56,7 +55,7 @@ internal object CardDomainModule { @Provides @Singleton fun provideDeleteSavedAccessCodesUseCase(tangemSdkManager: TangemSdkManager): DeleteSavedAccessCodesUseCase { - return DefaultDeleteSavedAccessCodesUseCase(tangemSdkManager) + return DeleteSavedAccessCodesUseCase(tangemSdkManager) } @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt deleted file mode 100644 index 9a89db05b8..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.tap.domain.card - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess -import com.tangem.domain.card.DeleteSavedAccessCodesUseCase -import com.tangem.sdk.api.TangemSdkManager - -internal class DefaultDeleteSavedAccessCodesUseCase( - private val tangemSdkManager: TangemSdkManager, -) : DeleteSavedAccessCodesUseCase { - - override suspend fun invoke(cardId: String): Either { - tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) - .doOnFailure { return it.left() } - .doOnSuccess { return Unit.right() } - - return Unit.right() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index c464e78a94..3d7b8d17ec 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -36,9 +36,9 @@ import com.tangem.tap.domain.userWalletList.utils.* import com.tangem.utils.Provider import com.tangem.utils.ProviderSuspend import com.tangem.utils.coroutines.runSuspendCatching -import dagger.Lazy import com.tangem.utils.extensions.addOrReplace import com.tangem.utils.extensions.indexOfFirstOrNull +import dagger.Lazy import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -96,8 +96,8 @@ internal class DefaultUserWalletsListRepository( ?: loadedWallets.firstOrNull()?.also { selectedUserWalletRepository.set(it.walletId) } - userWallets.value = loadedWallets setSelectedUserWallet(initialSelection) + userWallets.value = loadedWallets } } } @@ -235,7 +235,6 @@ internal class DefaultUserWalletsListRepository( ) } - userWallets.value = updatedWallets if (currentSelected != null) { if (newSelected == null) { onAllWalletsDeleted() @@ -243,6 +242,7 @@ internal class DefaultUserWalletsListRepository( selectedUserWalletRepository.set(newSelected?.walletId) setSelectedUserWallet(newSelected) } + userWallets.value = updatedWallets } @Suppress("CyclomaticComplexMethod", "LongMethod") diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt index d96d1fcce0..2f301e8206 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt @@ -197,6 +197,9 @@ internal class ResetCardModel @Inject constructor( checkRemainingBackupCards() } + .onLeft { + TangemLogger.e("Failed to reset card: $it") + } } } diff --git a/domain/card/build.gradle.kts b/domain/card/build.gradle.kts index 44e17e2ce6..abd1254e93 100644 --- a/domain/card/build.gradle.kts +++ b/domain/card/build.gradle.kts @@ -8,6 +8,10 @@ android { namespace = "com.tangem.domain.card" } +tasks.withType().configureEach { + useJUnitPlatform() +} + dependencies { implementation(projects.core.analytics.models) implementation(projects.core.error) @@ -25,6 +29,7 @@ dependencies { implementation(projects.domain.visa.models) implementation(projects.core.utils) + implementation(projects.libs.tangemSdkApi) implementation(tangemDeps.card.core) implementation(tangemDeps.blockchain) { @@ -32,9 +37,8 @@ dependencies { } /** Testing libraries */ - testImplementation(deps.test.junit5) testRuntimeOnly(deps.test.junit5.engine) - testImplementation(deps.test.mockk) - testImplementation(deps.test.truth) + testRuntimeOnly(deps.test.junit5.vintage.engine) testImplementation(projects.common.test) + testImplementation(projects.test.core) } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt index dc118e8f15..32902756ba 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt @@ -1,8 +1,33 @@ package com.tangem.domain.card import arrow.core.Either +import arrow.core.raise.either +import com.tangem.common.doOnFailure +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.logging.TangemLogger -interface DeleteSavedAccessCodesUseCase { +/** + * Removes saved user codes (access code and/or passcode) for a physical Tangem card + * from the device's secure storage. + * + * Typically invoked after a successful factory reset of the card, so that stale codes + * for an already-wiped card are not left on the device. + * + * @property tangemSdkManager Card SDK wrapper that performs the code removal operation + */ +class DeleteSavedAccessCodesUseCase( + private val tangemSdkManager: TangemSdkManager, +) { - suspend operator fun invoke(cardId: String): Either + /** + * @param cardId identifier of the card whose saved codes must be removed + * @return [Unit] on success; a Card SDK error (as [Throwable]) if removal failed + */ + suspend operator fun invoke(cardId: String): Either = either { + tangemSdkManager.deleteSavedUserCodes(cardsIds = setOf(cardId)) + .doOnFailure { error -> + TangemLogger.e("Failed to delete saved access codes for card with id: $cardId", error) + raise(error) + } + } } \ No newline at end of file diff --git a/domain/card/src/test/java/com/tangem/domain/card/DeleteSavedAccessCodesUseCaseTest.kt b/domain/card/src/test/java/com/tangem/domain/card/DeleteSavedAccessCodesUseCaseTest.kt new file mode 100644 index 0000000000..c5c0d63d9a --- /dev/null +++ b/domain/card/src/test/java/com/tangem/domain/card/DeleteSavedAccessCodesUseCaseTest.kt @@ -0,0 +1,69 @@ +package com.tangem.domain.card + +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.test.core.assertEitherLeft +import com.tangem.test.core.assertEitherRight +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 +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class DeleteSavedAccessCodesUseCaseTest { + + private val tangemSdkManager = mockk() + + private lateinit var useCase: DeleteSavedAccessCodesUseCase + + @BeforeEach + fun setup() { + clearMocks(tangemSdkManager) + useCase = DeleteSavedAccessCodesUseCase(tangemSdkManager = tangemSdkManager) + } + + @Test + fun `returns Right Unit when sdk deletes codes successfully`() = runTest { + // Arrange + val cardId = "AA00000000000001" + coEvery { tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) } returns CompletionResult.Success(Unit) + + // Act + val actual = useCase(cardId = cardId) + + // Assert + assertEitherRight(actual) + } + + @Test + fun `returns Left with sdk error when sdk fails`() = runTest { + // Arrange + val cardId = "AA00000000000002" + val sdkError = TangemSdkError.ExceptionError(RuntimeException("boom")) + coEvery { tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) } returns CompletionResult.Failure(sdkError) + + // Act + val actual = useCase(cardId = cardId) + + // Assert + assertEitherLeft(actual, sdkError) + } + + @Test + fun `passes exactly the given cardId as a singleton set to sdk`() = runTest { + // Arrange + val cardId = "AA00000000000003" + coEvery { tangemSdkManager.deleteSavedUserCodes(any()) } returns CompletionResult.Success(Unit) + + // Act + useCase(cardId = cardId) + + // Assert + coVerify(exactly = 1) { tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt index 4037df2361..431d52cb5f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt @@ -4,6 +4,7 @@ import arrow.core.Either 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.logging.TangemLogger /** * Use case for deleting user wallet @@ -24,8 +25,10 @@ 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 { - return userWalletsListRepository.delete(userWalletIds = listOf(userWalletId)).map { - userWalletsListRepository.selectedUserWallet.value != null - } + return userWalletsListRepository.delete(userWalletIds = listOf(userWalletId)) + .map { userWalletsListRepository.selectedUserWallet.value != null } + .onLeft { + TangemLogger.e("Failed to delete wallet with id ${userWalletId.value}: $it") + } } } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index cb69868e03..5bf1853472 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -228,6 +228,7 @@ test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", ve test-junit = { module = "junit:junit", version.ref = "junit" } test-junit5 = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } test-junit5-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" } +test-junit5-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" } test-junit-android = { module = "androidx.test.ext:junit", version.ref = "junitAndroidExt" } test-truth = { module = "com.google.truth:truth", version.ref = "truth" } test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } diff --git a/libs/tangem-sdk-api/build.gradle.kts b/libs/tangem-sdk-api/build.gradle.kts index bf167d3a08..1a6d4da84d 100644 --- a/libs/tangem-sdk-api/build.gradle.kts +++ b/libs/tangem-sdk-api/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) - alias(deps.plugins.kotlin.serialization) alias(deps.plugins.hilt.android) id("configuration") } @@ -12,11 +11,7 @@ android { } dependencies { - implementation(projects.common) implementation(projects.domain.models) - implementation(projects.domain.card) - implementation(projects.domain.legacy) - implementation(projects.domain.wallets.models) implementation(projects.domain.visa.models) implementation(projects.core.configToggles) @@ -28,8 +23,6 @@ dependencies { exclude(module = "joda-time") } - /** Other libraries */ - /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt deleted file mode 100644 index f27fd2b825..0000000000 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.sdk.api.di - -import android.content.Context -import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.operations.attestation.CardArtworksProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import java.io.File -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object CardSdkModule { - - @Provides - @Singleton - fun provideCardArtworksProvider( - sdkRepository: CardSdkConfigRepository, - @ApplicationContext context: Context, - ): CardArtworksProvider { - return CardArtworksProvider( - tangemApiBaseUrlProvider = { sdkRepository.sdk.config.tangemApiBaseUrl }, - artworksDirectory = File( - context.getExternalFilesDir(null) ?: context.filesDir, - "card_artworks", - ).apply { mkdirs() }, - ) - } -} \ No newline at end of file