mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
PM-25642: Force sync or clear last sync time on sync notification (#5958)
This commit is contained in:
parent
df63bb4b6c
commit
a02a84ee08
@ -15,9 +15,9 @@ import kotlinx.coroutines.flow.Flow
|
||||
*/
|
||||
interface PushManager {
|
||||
/**
|
||||
* Flow that represents requests intended for full syncs.
|
||||
* Flow that represents requests intended for full syncs for the user ID provided.
|
||||
*/
|
||||
val fullSyncFlow: Flow<Unit>
|
||||
val fullSyncFlow: Flow<String>
|
||||
|
||||
/**
|
||||
* Flow that represents requests intended to log a user out.
|
||||
|
||||
@ -55,7 +55,7 @@ class PushManagerImpl @Inject constructor(
|
||||
private val ioScope = CoroutineScope(dispatcherManager.io)
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
private val mutableFullSyncSharedFlow = bufferedMutableSharedFlow<Unit>()
|
||||
private val mutableFullSyncSharedFlow = bufferedMutableSharedFlow<String>()
|
||||
private val mutableLogoutSharedFlow = bufferedMutableSharedFlow<NotificationLogoutData>()
|
||||
private val mutablePasswordlessRequestSharedFlow =
|
||||
bufferedMutableSharedFlow<PasswordlessRequestData>()
|
||||
@ -73,7 +73,7 @@ class PushManagerImpl @Inject constructor(
|
||||
private val mutableSyncSendUpsertSharedFlow =
|
||||
bufferedMutableSharedFlow<SyncSendUpsertData>()
|
||||
|
||||
override val fullSyncFlow: SharedFlow<Unit>
|
||||
override val fullSyncFlow: SharedFlow<String>
|
||||
get() = mutableFullSyncSharedFlow.asSharedFlow()
|
||||
|
||||
override val logoutFlow: SharedFlow<NotificationLogoutData>
|
||||
@ -204,7 +204,10 @@ class PushManagerImpl @Inject constructor(
|
||||
NotificationType.SYNC_SETTINGS,
|
||||
NotificationType.SYNC_VAULT,
|
||||
-> {
|
||||
mutableFullSyncSharedFlow.tryEmit(Unit)
|
||||
json
|
||||
.decodeFromString<NotificationPayload.SyncNotification>(notification.payload)
|
||||
.userId
|
||||
?.let { mutableFullSyncSharedFlow.tryEmit(it) }
|
||||
}
|
||||
|
||||
NotificationType.SYNC_FOLDER_CREATE,
|
||||
|
||||
@ -83,4 +83,12 @@ sealed class NotificationPayload {
|
||||
@JsonNames("UserId", "userId") override val userId: String?,
|
||||
@JsonNames("Id", "id") val loginRequestId: String?,
|
||||
) : NotificationPayload()
|
||||
|
||||
/**
|
||||
* A notification payload for syncing a users vault.
|
||||
*/
|
||||
@Serializable
|
||||
data class SyncNotification(
|
||||
@JsonNames("UserId", "userId") override val userId: String?,
|
||||
) : NotificationPayload()
|
||||
}
|
||||
|
||||
@ -209,7 +209,13 @@ class VaultSyncManagerImpl(
|
||||
|
||||
pushManager
|
||||
.fullSyncFlow
|
||||
.onEach { sync(forced = false) }
|
||||
.onEach { userId ->
|
||||
if (userId == activeUserId) {
|
||||
sync(forced = false)
|
||||
} else {
|
||||
settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = null)
|
||||
}
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
databaseSchemeManager
|
||||
|
||||
@ -205,7 +205,7 @@ class ImportLoginsViewModel @Inject constructor(
|
||||
|
||||
private fun syncVault() {
|
||||
viewModelScope.launch {
|
||||
val result = vaultRepository.syncForResult()
|
||||
val result = vaultRepository.syncForResult(forced = true)
|
||||
sendAction(ImportLoginsAction.Internal.VaultSyncResultReceived(result))
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@ class PushManagerTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(SYNC_CIPHERS_NOTIFICATION_MAP)
|
||||
assertEquals(
|
||||
Unit,
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@ -180,7 +180,7 @@ class PushManagerTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(SYNC_SETTINGS_NOTIFICATION_MAP)
|
||||
assertEquals(
|
||||
Unit,
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@ -191,7 +191,7 @@ class PushManagerTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(SYNC_VAULT_NOTIFICATION_MAP)
|
||||
assertEquals(
|
||||
Unit,
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@ -580,7 +580,7 @@ class PushManagerTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(SYNC_CIPHERS_NOTIFICATION_MAP)
|
||||
assertEquals(
|
||||
Unit,
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@ -599,7 +599,7 @@ class PushManagerTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(SYNC_SETTINGS_NOTIFICATION_MAP)
|
||||
assertEquals(
|
||||
Unit,
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@ -610,7 +610,7 @@ class PushManagerTest {
|
||||
pushManager.fullSyncFlow.test {
|
||||
pushManager.onMessageReceived(SYNC_VAULT_NOTIFICATION_MAP)
|
||||
assertEquals(
|
||||
Unit,
|
||||
"078966a2-93c2-4618-ae2a-0a2394c88d37",
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ class VaultSyncManagerTest {
|
||||
private val userLogoutManager: UserLogoutManager = mockk {
|
||||
every { softLogout(any(), any()) } just runs
|
||||
}
|
||||
private val mutableFullSyncFlow = bufferedMutableSharedFlow<Unit>()
|
||||
private val mutableFullSyncFlow = bufferedMutableSharedFlow<String>()
|
||||
private val pushManager: PushManager = mockk {
|
||||
every { fullSyncFlow } returns mutableFullSyncFlow
|
||||
}
|
||||
@ -1105,15 +1105,27 @@ class VaultSyncManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fullSyncFlow emission should trigger unforced sync`() {
|
||||
fun `fullSyncFlow emission with active user ID should trigger an unforced sync`() {
|
||||
val userId = "mockId-1"
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
every { settingsDiskSource.getLastSyncTime(userId = userId) } returns null
|
||||
coEvery { syncService.sync() } just awaits
|
||||
|
||||
mutableFullSyncFlow.tryEmit(Unit)
|
||||
mutableFullSyncFlow.tryEmit(userId)
|
||||
|
||||
coVerify { syncService.sync() }
|
||||
coVerify(exactly = 1) { syncService.sync() }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `fullSyncFlow emission with non-active user ID should clear last sync time for that user`() {
|
||||
val userId = "mockId-2"
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
mutableFullSyncFlow.tryEmit(userId)
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
||||
@ -33,7 +33,9 @@ import org.junit.jupiter.api.Test
|
||||
class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
coEvery { syncForResult() } returns SyncVaultDataResult.Success(itemsAvailable = true)
|
||||
coEvery {
|
||||
syncForResult(forced = true)
|
||||
} returns SyncVaultDataResult.Success(itemsAvailable = true)
|
||||
}
|
||||
|
||||
private val firstTimeActionManager: FirstTimeActionManager = mockk {
|
||||
@ -312,21 +314,23 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
coVerify { vaultRepository.syncForResult() }
|
||||
coVerify(exactly = 1) { vaultRepository.syncForResult(forced = true) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RetryVaultSync sets isVaultSyncing to true and clears dialog state and calls syncForResult`() =
|
||||
runTest {
|
||||
coEvery { vaultRepository.syncForResult() } returns SyncVaultDataResult.Error(Exception())
|
||||
coEvery {
|
||||
vaultRepository.syncForResult(forced = true)
|
||||
} returns SyncVaultDataResult.Error(Exception())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(ImportLoginsAction.MoveToSyncInProgress)
|
||||
viewModel.stateFlow.test {
|
||||
assertNotNull(awaitItem().dialogState)
|
||||
coEvery { vaultRepository.syncForResult() } returns SyncVaultDataResult.Success(
|
||||
itemsAvailable = true,
|
||||
)
|
||||
coEvery {
|
||||
vaultRepository.syncForResult(forced = true)
|
||||
} returns SyncVaultDataResult.Success(itemsAvailable = true)
|
||||
viewModel.trySendAction(ImportLoginsAction.RetryVaultSync)
|
||||
assertEquals(
|
||||
ImportLoginsState(
|
||||
@ -339,7 +343,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
coVerify { vaultRepository.syncForResult() }
|
||||
coVerify(exactly = 2) { vaultRepository.syncForResult(forced = true) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@ -367,7 +371,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
fun `MoveToSyncInProgress should set no items imported error dialog state when sync succeeds but no items are available`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
vaultRepository.syncForResult()
|
||||
vaultRepository.syncForResult(forced = true)
|
||||
} returns SyncVaultDataResult.Success(itemsAvailable = false)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
@ -402,7 +406,9 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `SyncVaultDataResult Error should remove loading state and show error dialog`() = runTest {
|
||||
coEvery { vaultRepository.syncForResult() } returns SyncVaultDataResult.Error(Exception())
|
||||
coEvery {
|
||||
vaultRepository.syncForResult(forced = true)
|
||||
} returns SyncVaultDataResult.Error(Exception())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(ImportLoginsAction.MoveToSyncInProgress)
|
||||
assertEquals(
|
||||
@ -420,7 +426,7 @@ class ImportLoginsViewModelTest : BaseViewModelTest() {
|
||||
fun `FailSyncAcknowledged should remove dialog state and send NavigateBack event`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
vaultRepository.syncForResult()
|
||||
vaultRepository.syncForResult(forced = true)
|
||||
} returns SyncVaultDataResult.Error(Exception())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user