Offer backup restore after relogin even when publishing is disabled

maybeOfferRestore gated on the BACKUP_APPLICATIONS pref, but
updatePrefsForLogout wipes prefs_<npub> by design, so after a
logout->login round trip the flag always read false and the restore
prompt could never appear - the exact case restore exists for. Fetching
is a read of a public self-authored event, so drop the publish-consent
gate and rely on the local-apps check; publishing still requires the
toggle.
This commit is contained in:
greenart7c3
2026-09-16 09:33:12 -03:00
parent 1f3aa1f327
commit 1e494a21a3
2 changed files with 81 additions and 1 deletions
@@ -211,7 +211,12 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
fun maybeOfferRestore(account: Account) { fun maybeOfferRestore(account: Account) {
if (BuildFlavorChecker.isOfflineFlavor()) return if (BuildFlavorChecker.isOfflineFlavor()) return
if (!LocalPreferences.getBackupApplications(Amber.instance, account.npub)) return
// No BACKUP_APPLICATIONS gate here: that flag is publish consent for the
// daily worker and is deliberately wiped on logout, so it can never be
// true on a logout→login round trip — the main case restore exists for.
// Fetching is a read of a public, self-authored event; the dao check
// below skips accounts whose local apps are still present.
Amber.instance.applicationIOScope.launch { Amber.instance.applicationIOScope.launch {
try { try {
@@ -0,0 +1,75 @@
package com.greenart7c3.nostrsigner.ui
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.BuildFlavorChecker
import com.greenart7c3.nostrsigner.LocalPreferences
import com.greenart7c3.nostrsigner.database.ApplicationDao
import com.greenart7c3.nostrsigner.service.ApplicationBackup
import com.greenart7c3.nostrsigner.service.installAmberInstance
import com.greenart7c3.nostrsigner.service.newTestAccount
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.junit.After
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
class AccountStateViewModelRestoreTest {
// Unconfined so maybeOfferRestore's internal launch runs to completion on the
// calling thread; every ApplicationBackup call it makes is a mocked immediate.
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)
@Before
fun setUp() {
mockkObject(BuildFlavorChecker)
every { BuildFlavorChecker.isOfflineFlavor() } returns false
mockkObject(LocalPreferences)
every { LocalPreferences.currentAccount(any()) } returns null
every { LocalPreferences.allSavedAccounts(any()) } returns emptyList()
coEvery { LocalPreferences.warmAccountCache(any()) } returns Unit
every { LocalPreferences.loadFromEncryptedStorageSync(any(), any()) } returns null
// Post-logout state: updatePrefsForLogout wipes prefs_<npub>, so the
// publish-consent flag reads false on relogin.
every { LocalPreferences.getBackupApplications(any(), any()) } returns false
val dao = mockk<ApplicationDao>(relaxed = true)
coEvery { dao.getAll(any()) } returns emptyList()
val amber = mockk<Amber>(relaxed = true)
every { amber.applicationIOScope } returns scope
every { amber.dao(any()) } returns dao
installAmberInstance(amber)
mockkObject(ApplicationBackup)
coEvery { ApplicationBackup.resolveReadRelays(any()) } returns setOf(mockk())
coEvery { ApplicationBackup.fetchLatestBackupEvent(any(), any()) } returns null
}
@After
fun tearDown() {
unmockkObject(BuildFlavorChecker)
unmockkObject(LocalPreferences)
unmockkObject(ApplicationBackup)
}
@Test
fun `maybeOfferRestore reaches relay fetch even with backup publishing disabled`() {
val viewModel = AccountStateViewModel(null)
val account = newTestAccount(scope)
viewModel.maybeOfferRestore(account)
coVerify {
ApplicationBackup.resolveReadRelays(account)
ApplicationBackup.fetchLatestBackupEvent(account, any())
}
assertNull(viewModel.restorePrompt.value)
}
}