diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt index 857f696f..ffaf389c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt @@ -211,7 +211,12 @@ class AccountStateViewModel(npub: String?) : ViewModel() { fun maybeOfferRestore(account: Account) { 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 { try { diff --git a/app/src/test/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModelRestoreTest.kt b/app/src/test/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModelRestoreTest.kt new file mode 100644 index 00000000..92b6c5a6 --- /dev/null +++ b/app/src/test/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModelRestoreTest.kt @@ -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_, so the + // publish-consent flag reads false on relogin. + every { LocalPreferences.getBackupApplications(any(), any()) } returns false + + val dao = mockk(relaxed = true) + coEvery { dao.getAll(any()) } returns emptyList() + val amber = mockk(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) + } +}