From ed99c6d5f5fa078de275b4d81d757afea3a9c437 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 17 Jul 2026 11:13:30 -0300 Subject: [PATCH 1/3] test: regression test for simulate crash on paykit failure --- .../to/bitkit/repositories/BackupRepoTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/src/test/java/to/bitkit/repositories/BackupRepoTest.kt b/app/src/test/java/to/bitkit/repositories/BackupRepoTest.kt index 84bafb834..6a9078c29 100644 --- a/app/src/test/java/to/bitkit/repositories/BackupRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/BackupRepoTest.kt @@ -40,6 +40,7 @@ import to.bitkit.services.PaykitSdkService import to.bitkit.test.BaseUnitTest import to.bitkit.utils.AppError import javax.inject.Provider +import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.time.Clock import kotlin.time.ExperimentalTime @@ -111,6 +112,35 @@ class BackupRepoTest : BaseUnitTest() { verify(settingsStore, never()).update(any()) } + @Test + fun `automatic wallet backup marks failure when Paykit snapshot fails`() = test { + whenever { privatePaykitRepo.backupSnapshot() } + .thenReturn(Result.failure(BackupRepoTestError("paykit session missing capabilities"))) + val backupStatuses = MutableStateFlow( + mapOf( + BackupCategory.WALLET to BackupItemStatus( + synced = 1_000, + required = 2_000, + ), + ) + ) + val allowWalletClear = CompletableDeferred().apply { complete(Unit) } + stubBackupStatuses(backupStatuses, allowWalletClear) {} + stubBackupObservers() + + try { + sut.startObservingBackups() + runCurrent() + advanceTimeBy(5_000) + runCurrent() + + verify(vssBackupClient, never()).putObject(eq(BackupCategory.WALLET.name), any()) + assertFalse(backupStatuses.value.getValue(BackupCategory.WALLET).running) + } finally { + sut.stopObservingBackups() + } + } + @Test fun `start observing backs up stale required status after clearing running flag`() = test { val backupStatuses = MutableStateFlow( From 41139bd3d60f2318b323b7c879bf49d24ed8d53a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 17 Jul 2026 11:22:03 -0300 Subject: [PATCH 2/3] fix: handle paykit snapshot failure in wallet backup --- .../java/to/bitkit/repositories/BackupRepo.kt | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/BackupRepo.kt b/app/src/main/java/to/bitkit/repositories/BackupRepo.kt index 0d681c748..51fa0f0b7 100644 --- a/app/src/main/java/to/bitkit/repositories/BackupRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BackupRepo.kt @@ -44,6 +44,7 @@ import to.bitkit.di.IoDispatcher import to.bitkit.di.json import to.bitkit.ext.formatPlural import to.bitkit.ext.nowMillis +import to.bitkit.ext.runSuspendCatching import to.bitkit.models.ActivityBackupV1 import to.bitkit.models.BackupCategory import to.bitkit.models.BackupItemStatus @@ -435,7 +436,11 @@ class BackupRepo @Inject constructor( it.copy(running = true, required = backupRequired) } - vssBackupClient.putObject(key = category.name, data = getBackupDataBytes(category)) + val data = runSuspendCatching { getBackupDataBytes(category) } + .onFailure { markBackupFailed(category, backupRequired, it) } + .getOrNull() ?: return@withContext + + vssBackupClient.putObject(key = category.name, data = data) .onSuccess { runningBackups -= category failedBackupRequired -= category @@ -447,18 +452,20 @@ class BackupRepo @Inject constructor( } Logger.info("Backup succeeded for: '$category'", context = TAG) } - .onFailure { e -> - runningBackups -= category - cacheStore.updateBackupStatus(category) { - if (it.required == backupRequired) { - failedBackupRequired[category] = backupRequired - } else { - failedBackupRequired -= category - } - it.copy(running = false) - } - Logger.error("Backup failed for: '$category'", e, context = TAG) + .onFailure { markBackupFailed(category, backupRequired, it) } + } + + private suspend fun markBackupFailed(category: BackupCategory, backupRequired: Long, e: Throwable) { + runningBackups -= category + cacheStore.updateBackupStatus(category) { + if (it.required == backupRequired) { + failedBackupRequired[category] = backupRequired + } else { + failedBackupRequired -= category } + it.copy(running = false) + } + Logger.error("Backup failed for: '$category'", e, context = TAG) } private suspend fun getBackupDataBytes(category: BackupCategory): ByteArray = when (category) { From 2ba4d65d74d2429fe30e9e0355d2a6d697b6138d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 17 Jul 2026 11:34:56 -0300 Subject: [PATCH 3/3] chore: add changelog fragment --- changelog.d/next/1092.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/next/1092.fixed.md diff --git a/changelog.d/next/1092.fixed.md b/changelog.d/next/1092.fixed.md new file mode 100644 index 000000000..b4fc5af7a --- /dev/null +++ b/changelog.d/next/1092.fixed.md @@ -0,0 +1 @@ +Fixed a crash during automatic wallet backup when Paykit state could not be read.