diff --git a/app/src/main/java/to/bitkit/repositories/ContactPaymentSettingsRepo.kt b/app/src/main/java/to/bitkit/repositories/ContactPaymentSettingsRepo.kt index 9603d6ca08..529d71c5d3 100644 --- a/app/src/main/java/to/bitkit/repositories/ContactPaymentSettingsRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ContactPaymentSettingsRepo.kt @@ -76,7 +76,9 @@ class ContactPaymentSettingsRepo @Inject constructor( private suspend fun enable(contacts: List): Result { val previous = settingsStore.data.first() - val canUsePrivateContactPayments = privatePaykitRepo.hasPrivatePaymentAccess() + val canUsePrivateContactPayments = runSuspendCatching { + privatePaykitRepo.hasPrivatePaymentAccess() + }.getOrElse { return Result.failure(it) } return runSuspendCatching { settingsStore.update { it.copy( diff --git a/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt b/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt index a539365f76..14d4f417bd 100644 --- a/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt @@ -153,7 +153,7 @@ class PrivatePaykitRepo @Inject constructor( suspend fun reconcileReservedReceiveIndexes(): Result = addressReservationRepo.reconcileReservedIndexesWithLdk() - suspend fun hasPrivatePaymentAccess(): Boolean = hasPrivatePaymentAccessForCurrentProfile() + suspend fun hasPrivatePaymentAccess(): Boolean = paykitSdkService.hasPrivatePaymentAccess() suspend fun prepareSavedContacts( publicKeys: Collection, diff --git a/app/src/test/java/to/bitkit/repositories/ContactPaymentSettingsRepoTest.kt b/app/src/test/java/to/bitkit/repositories/ContactPaymentSettingsRepoTest.kt index cbbf15cafe..2245d3402a 100644 --- a/app/src/test/java/to/bitkit/repositories/ContactPaymentSettingsRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/ContactPaymentSettingsRepoTest.kt @@ -19,6 +19,7 @@ import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import to.bitkit.data.SettingsData import to.bitkit.data.SettingsStore @@ -108,6 +109,62 @@ class ContactPaymentSettingsRepoTest : BaseUnitTest() { verify(pubkyRepo, never()).hasSecretKey() } + @Test + fun `failed access check preserves settings and allows retry`() = test { + val previous = SettingsData( + sharesPrivatePaykitEndpoints = true, + publicPaykitLightningEnabled = false, + publicPaykitOnchainEnabled = false, + ) + settingsFlow.value = previous + val failure = ContactPaymentSettingsTestError("Paykit unavailable") + var accessAvailable = false + whenever(privatePaykitRepo.hasPrivatePaymentAccess()).thenAnswer { + if (!accessAvailable) throw failure + true + } + val sut = createSut() + + assertSame(failure, sut.setEnabled(true).exceptionOrNull()) + assertEquals(previous, settingsFlow.value) + verify(settingsStore, never()).update(any()) + verifyNoInteractions(publicPaykitRepo) + verify(privatePaykitRepo, never()).enableSharingAndPrepareSavedContacts(any>()) + verify(privatePaykitRepo, never()).disableSharingAndPruneUnsavedContactState(any>()) + + accessAvailable = true + + assertTrue(sut.setEnabled(true).isSuccess) + assertTrue(settingsFlow.value.sharesPrivatePaykitEndpoints) + verify(publicPaykitRepo).syncPublishedEndpoints(publish = true) + verify(privatePaykitRepo).enableSharingAndPrepareSavedContacts(listOf(CONTACT_KEY)) + } + + @Test + fun `cancelling access check leaves settings unchanged and releases sharing lock`() = test { + var waitForAccess = true + whenever(privatePaykitRepo.hasPrivatePaymentAccess()).doSuspendableAnswer { + if (waitForAccess) awaitCancellation() + true + } + val previous = settingsFlow.value + val sut = createSut() + val enable = async { sut.setEnabled(true) } + runCurrent() + verify(privatePaykitRepo).hasPrivatePaymentAccess() + + enable.cancelAndJoin() + + assertEquals(previous, settingsFlow.value) + verify(settingsStore, never()).update(any()) + verifyNoInteractions(publicPaykitRepo) + verify(privatePaykitRepo, never()).enableSharingAndPrepareSavedContacts(any>()) + verify(privatePaykitRepo, never()).disableSharingAndPruneUnsavedContactState(any>()) + waitForAccess = false + + assertTrue(sut.setEnabled(true).isSuccess) + } + @Test fun `failed publication restores disabled settings`() = test { whenever { publicPaykitRepo.syncPublishedEndpoints(publish = true) } diff --git a/app/src/test/java/to/bitkit/repositories/PrivatePaykitRepoTest.kt b/app/src/test/java/to/bitkit/repositories/PrivatePaykitRepoTest.kt index 163098c5a6..1eebb5a291 100644 --- a/app/src/test/java/to/bitkit/repositories/PrivatePaykitRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/PrivatePaykitRepoTest.kt @@ -260,17 +260,19 @@ class PrivatePaykitRepoTest : BaseUnitTest(StandardTestDispatcher()) { } @Test - fun `hasPrivatePaymentAccess returns false when the SDK check fails`() = test { - var accessFails = true - whenever(paykitSdkService.hasPrivatePaymentAccess()).thenAnswer { - check(!accessFails) { "Paykit unavailable" } - true + fun `hasPrivatePaymentAccess propagates SDK errors and cancellation`() = test { + val failures = listOf(AppError("Paykit unavailable"), CancellationException("Cancelled")) + var failure: Throwable = failures.first() + whenever(paykitSdkService.hasPrivatePaymentAccess()).thenAnswer { throw failure } + for (nextFailure in failures) { + failure = nextFailure + assertEquals(failure, assertFailsWith { sut.hasPrivatePaymentAccess() }) } + } - assertFalse(sut.hasPrivatePaymentAccess()) - + @Test + fun `prepareSavedContacts propagates identity errors and cancellation`() = test { settingsData.value = SettingsData(sharesPrivatePaykitEndpoints = true, publicPaykitLightningEnabled = false) - accessFails = false val failures = listOf(AppError("Identity unavailable"), CancellationException("Cancelled")) var failure: Throwable = failures.first() whenever(paykitSdkService.identityStatus()).thenAnswer { throw failure } diff --git a/changelog.d/next/1444.fixed.md b/changelog.d/next/1444.fixed.md new file mode 100644 index 0000000000..1499325ef7 --- /dev/null +++ b/changelog.d/next/1444.fixed.md @@ -0,0 +1 @@ +Contact payment setup now reports temporary access failures instead of silently disabling private payments.