diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt index 1d332973e9..5dfdd0f0cf 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt @@ -34,6 +34,8 @@ import java.time.Clock import java.time.ZoneOffset import java.time.ZonedDateTime import javax.inject.Inject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.toJavaDuration @@ -134,7 +136,6 @@ class PushManagerImpl @Inject constructor( @Suppress("LongMethod", "CyclomaticComplexMethod") private fun onMessageReceived(notification: BitwardenNotification) { if (authDiskSource.uniqueAppId == notification.contextId) return - val userId = activeUserId ?: return Timber.d("Push Notification Received: ${notification.notificationType}") when (val type = notification.notificationType) { @@ -179,11 +180,13 @@ class PushManagerImpl @Inject constructor( .decodeFromString( string = notification.payload, ) - .takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) } - ?.takeIf { it.cipherId != null && it.revisionDate != null } + .takeIf { + it.cipherId != null && it.revisionDate != null && isLoggedIn(it.userId) + } ?.let { mutableSyncCipherUpsertSharedFlow.tryEmit( SyncCipherUpsertData( + userId = requireNotNull(it.userId), cipherId = requireNotNull(it.cipherId), revisionDate = requireNotNull(it.revisionDate), organizationId = it.organizationId, @@ -228,11 +231,13 @@ class PushManagerImpl @Inject constructor( .decodeFromString( string = notification.payload, ) - .takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) } - ?.takeIf { it.folderId != null && it.revisionDate != null } + .takeIf { + it.folderId != null && it.revisionDate != null && isLoggedIn(it.userId) + } ?.let { mutableSyncFolderUpsertSharedFlow.tryEmit( SyncFolderUpsertData( + userId = requireNotNull(it.userId), folderId = requireNotNull(it.folderId), revisionDate = requireNotNull(it.revisionDate), isUpdate = type == NotificationType.SYNC_FOLDER_UPDATE, @@ -273,11 +278,13 @@ class PushManagerImpl @Inject constructor( .decodeFromString( string = notification.payload, ) - .takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) } - ?.takeIf { it.sendId != null && it.revisionDate != null } + .takeIf { + it.sendId != null && it.revisionDate != null && isLoggedIn(it.userId) + } ?.let { mutableSyncSendUpsertSharedFlow.tryEmit( SyncSendUpsertData( + userId = requireNotNull(it.userId), sendId = requireNotNull(it.sendId), revisionDate = requireNotNull(it.revisionDate), isUpdate = type == NotificationType.SYNC_SEND_UPDATE, @@ -361,11 +368,11 @@ class PushManagerImpl @Inject constructor( ) } + @OptIn(ExperimentalContracts::class) private fun isLoggedIn( - userId: String, - ): Boolean = authDiskSource.getAccountTokens(userId)?.isLoggedIn == true -} - -private fun NotificationPayload.userMatchesNotification(userId: String): Boolean { - return this.userId != null && this.userId == userId + userId: String?, + ): Boolean { + contract { returns(true) implies (userId != null) } + return userId?.let { authDiskSource.getAccountTokens(it) }?.isLoggedIn == true + } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncCipherUpsertData.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncCipherUpsertData.kt index 02f1bce7f5..3f322b3bdb 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncCipherUpsertData.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncCipherUpsertData.kt @@ -5,12 +5,14 @@ import java.time.ZonedDateTime /** * Required data for sync cipher upsert operations. * + * @property userId The user ID associated with this update. * @property cipherId The cipher ID. * @property revisionDate The cipher's revision date. This is used to determine if the local copy of * the cipher is out-of-date. * @property isUpdate Whether or not this is an update of an existing cipher. */ data class SyncCipherUpsertData( + val userId: String, val cipherId: String, val revisionDate: ZonedDateTime, val organizationId: String?, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncFolderUpsertData.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncFolderUpsertData.kt index 8d45786e9a..7d07117575 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncFolderUpsertData.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncFolderUpsertData.kt @@ -5,12 +5,14 @@ import java.time.ZonedDateTime /** * Required data for sync folder upsert operations. * + * @property userId The user ID associated with this update. * @property folderId The folder ID. * @property revisionDate The folder's revision date. This is used to determine if the local copy of * the folder is out-of-date. * @property isUpdate Whether or not this is an update of an existing folder. */ data class SyncFolderUpsertData( + val userId: String, val folderId: String, val revisionDate: ZonedDateTime, val isUpdate: Boolean, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncSendUpsertData.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncSendUpsertData.kt index 37039c21b0..10397bcecf 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncSendUpsertData.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SyncSendUpsertData.kt @@ -5,12 +5,14 @@ import java.time.ZonedDateTime /** * Required data for sync send upsert operations. * + * @property userId The user ID associated with this update. * @property sendId The send ID. * @property revisionDate The send's revision date. This is used to determine if the local copy of * the send is out-of-date. * @property isUpdate Whether or not this is an update of an existing send. */ data class SyncSendUpsertData( + val userId: String, val sendId: String, val revisionDate: ZonedDateTime, val isUpdate: Boolean, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt index 40a9e669a7..cb5d391bc8 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt @@ -17,6 +17,7 @@ import com.bitwarden.vault.AttachmentView import com.bitwarden.vault.CipherView import com.bitwarden.vault.EncryptionContext import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager @@ -53,6 +54,7 @@ import java.time.Clock class CipherManagerImpl( private val fileManager: FileManager, private val authDiskSource: AuthDiskSource, + private val settingsDiskSource: SettingsDiskSource, private val ciphersService: CiphersService, private val vaultDiskSource: VaultDiskSource, private val vaultSdkSource: VaultSdkSource, @@ -689,7 +691,7 @@ class CipherManagerImpl( * for now. */ private suspend fun syncCipherIfNecessary(syncCipherUpsertData: SyncCipherUpsertData) { - val userId = activeUserId ?: return + val userId = syncCipherUpsertData.userId val cipherId = syncCipherUpsertData.cipherId val organizationId = syncCipherUpsertData.organizationId val collectionIds = syncCipherUpsertData.collectionIds @@ -732,6 +734,12 @@ class CipherManagerImpl( } if (!shouldUpdate) return + if (activeUserId != userId) { + // We cannot update right now since the accounts do not match, so we will + // do a full-sync on the next check. + settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = null) + return + } ciphersService .getCipher(cipherId = cipherId) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerImpl.kt index 8c868b7821..f54facfcca 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerImpl.kt @@ -6,6 +6,7 @@ import com.bitwarden.network.model.UpdateFolderResponseJson import com.bitwarden.network.service.FolderService import com.bitwarden.vault.FolderView import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData @@ -25,8 +26,10 @@ import kotlinx.coroutines.flow.onEach /** * The default implementation of the [FolderManager]. */ +@Suppress("LongParameterList") class FolderManagerImpl( private val authDiskSource: AuthDiskSource, + private val settingsDiskSource: SettingsDiskSource, private val folderService: FolderService, private val vaultDiskSource: VaultDiskSource, private val vaultSdkSource: VaultSdkSource, @@ -148,7 +151,7 @@ class FolderManagerImpl( * are met. */ private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) { - val userId = activeUserId ?: return + val userId = syncFolderUpsertData.userId val folderId = syncFolderUpsertData.folderId val isUpdate = syncFolderUpsertData.isUpdate val revisionDate = syncFolderUpsertData.revisionDate @@ -162,6 +165,12 @@ class FolderManagerImpl( localFolder.revisionDate.toEpochSecond() < revisionDate.toEpochSecond() if (!isValidCreate && !isValidUpdate) return + if (activeUserId != userId) { + // We cannot update right now since the accounts do not match, so we will + // do a full-sync on the next check. + settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = null) + return + } folderService .getFolder(folderId = folderId) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt index 3fcf6ecd6f..70f272e2b2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt @@ -13,6 +13,7 @@ import com.bitwarden.send.Send import com.bitwarden.send.SendType import com.bitwarden.send.SendView import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager @@ -38,6 +39,7 @@ import retrofit2.HttpException @Suppress("LongParameterList") class SendManagerImpl( private val authDiskSource: AuthDiskSource, + private val settingsDiskSource: SettingsDiskSource, private val vaultDiskSource: VaultDiskSource, private val vaultSdkSource: VaultSdkSource, private val sendsService: SendsService, @@ -265,7 +267,7 @@ class SendManagerImpl( * now. */ private suspend fun syncSendIfNecessary(syncSendUpsertData: SyncSendUpsertData) { - val userId = activeUserId ?: return + val userId = syncSendUpsertData.userId val sendId = syncSendUpsertData.sendId val isUpdate = syncSendUpsertData.isUpdate val revisionDate = syncSendUpsertData.revisionDate @@ -278,6 +280,12 @@ class SendManagerImpl( localSend != null && localSend.revisionDate.toEpochSecond() < revisionDate.toEpochSecond() if (!isValidCreate && !isValidUpdate) return + if (activeUserId != userId) { + // We cannot update right now since the accounts do not match, so we will + // do a full-sync on the next check. + settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = null) + return + } sendsService .getSend(sendId = sendId) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt index 065f656b77..289e32777a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt @@ -61,6 +61,7 @@ object VaultManagerModule { @Singleton fun provideCipherManager( ciphersService: CiphersService, + settingsDiskSource: SettingsDiskSource, vaultDiskSource: VaultDiskSource, vaultSdkSource: VaultSdkSource, authDiskSource: AuthDiskSource, @@ -71,6 +72,7 @@ object VaultManagerModule { pushManager: PushManager, ): CipherManager = CipherManagerImpl( fileManager = fileManager, + settingsDiskSource = settingsDiskSource, authDiskSource = authDiskSource, ciphersService = ciphersService, vaultDiskSource = vaultDiskSource, @@ -85,6 +87,7 @@ object VaultManagerModule { @Singleton fun provideFolderManager( folderService: FolderService, + settingsDiskSource: SettingsDiskSource, vaultDiskSource: VaultDiskSource, vaultSdkSource: VaultSdkSource, authDiskSource: AuthDiskSource, @@ -92,6 +95,7 @@ object VaultManagerModule { pushManager: PushManager, ): FolderManager = FolderManagerImpl( authDiskSource = authDiskSource, + settingsDiskSource = settingsDiskSource, folderService = folderService, vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, @@ -106,6 +110,7 @@ object VaultManagerModule { vaultDiskSource: VaultDiskSource, vaultSdkSource: VaultSdkSource, authDiskSource: AuthDiskSource, + settingsDiskSource: SettingsDiskSource, fileManager: FileManager, reviewPromptManager: ReviewPromptManager, pushManager: PushManager, @@ -113,6 +118,7 @@ object VaultManagerModule { ): SendManager = SendManagerImpl( fileManager = fileManager, authDiskSource = authDiskSource, + settingsDiskSource = settingsDiskSource, sendsService = sendsService, vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerTest.kt index 6e1e5272fc..2b85173351 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/PushManagerTest.kt @@ -260,6 +260,7 @@ class PushManagerTest { pushManager.onMessageReceived(SYNC_CIPHER_CREATE_NOTIFICATION_MAP) assertEquals( SyncCipherUpsertData( + userId = "078966a2-93c2-4618-ae2a-0a2394c88d37", cipherId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321", organizationId = "6a41d965-ed95-4eae-98c3-5f1ec609c2c1", collectionIds = listOf(), @@ -293,6 +294,7 @@ class PushManagerTest { pushManager.onMessageReceived(SYNC_CIPHER_UPDATE_NOTIFICATION_MAP) assertEquals( SyncCipherUpsertData( + userId = "078966a2-93c2-4618-ae2a-0a2394c88d37", cipherId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321", organizationId = "6a41d965-ed95-4eae-98c3-5f1ec609c2c1", collectionIds = listOf(), @@ -311,6 +313,7 @@ class PushManagerTest { pushManager.onMessageReceived(SYNC_FOLDER_CREATE_NOTIFICATION_MAP) assertEquals( SyncFolderUpsertData( + userId = "078966a2-93c2-4618-ae2a-0a2394c88d37", folderId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321", revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"), isUpdate = false, @@ -342,6 +345,7 @@ class PushManagerTest { pushManager.onMessageReceived(SYNC_FOLDER_UPDATE_NOTIFICATION_MAP) assertEquals( SyncFolderUpsertData( + userId = "078966a2-93c2-4618-ae2a-0a2394c88d37", folderId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321", revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"), isUpdate = true, @@ -372,6 +376,7 @@ class PushManagerTest { pushManager.onMessageReceived(SYNC_SEND_CREATE_NOTIFICATION_MAP) assertEquals( SyncSendUpsertData( + userId = "078966a2-93c2-4618-ae2a-0a2394c88d37", sendId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321", revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"), isUpdate = false, @@ -401,6 +406,7 @@ class PushManagerTest { pushManager.onMessageReceived(SYNC_SEND_UPDATE_NOTIFICATION_MAP) assertEquals( SyncSendUpsertData( + userId = "078966a2-93c2-4618-ae2a-0a2394c88d37", sendId = "aab5cdcc-f4a7-4e65-bf6d-5e0eab052321", revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00.000Z"), isUpdate = true, @@ -437,12 +443,13 @@ class PushManagerTest { } @Test - fun `onMessageReceived with sync cipher create does nothing`() = runTest { - pushManager.syncCipherUpsertFlow.test { - pushManager.onMessageReceived(SYNC_CIPHER_CREATE_NOTIFICATION_MAP) - expectNoEvents() + fun `onMessageReceived with sync cipher create emits to syncCipherUpsertFlow`() = + runTest { + pushManager.syncCipherUpsertFlow.test { + pushManager.onMessageReceived(SYNC_CIPHER_CREATE_NOTIFICATION_MAP) + expectNoEvents() + } } - } @Test fun `onMessageReceived with sync cipher delete does nothing`() = runTest { @@ -459,12 +466,13 @@ class PushManagerTest { } @Test - fun `onMessageReceived with sync cipher update does nothing`() = runTest { - pushManager.syncCipherUpsertFlow.test { - pushManager.onMessageReceived(SYNC_CIPHER_UPDATE_NOTIFICATION_MAP) - expectNoEvents() + fun `onMessageReceived with sync cipher update emits to syncCipherUpsertFlow`() = + runTest { + pushManager.syncCipherUpsertFlow.test { + pushManager.onMessageReceived(SYNC_CIPHER_UPDATE_NOTIFICATION_MAP) + expectNoEvents() + } } - } @Test fun `onMessageReceived with sync folder create does nothing`() = runTest { @@ -543,62 +551,6 @@ class PushManagerTest { } } - @Nested - inner class NullUserState { - @BeforeEach - fun setUp() { - authDiskSource.userState = null - } - - @Test - fun `onMessageReceived with logout does nothing`() = runTest { - pushManager.logoutFlow.test { - pushManager.onMessageReceived(LOGOUT_NOTIFICATION_MAP) - expectNoEvents() - } - } - - @Test - fun `onMessageReceived with logout with kdf reason does nothing`() = runTest { - pushManager.logoutFlow.test { - pushManager.onMessageReceived(LOGOUT_KDF_NOTIFICATION_MAP) - expectNoEvents() - } - } - - @Test - fun `onMessageReceived with sync ciphers does nothing`() = runTest { - pushManager.fullSyncFlow.test { - pushManager.onMessageReceived(SYNC_CIPHERS_NOTIFICATION_MAP) - expectNoEvents() - } - } - - @Test - fun `onMessageReceived with sync org keys does nothing`() = runTest { - pushManager.fullSyncFlow.test { - pushManager.onMessageReceived(SYNC_ORG_KEYS_NOTIFICATION_MAP) - expectNoEvents() - } - } - - @Test - fun `onMessageReceived with sync settings does nothing`() = runTest { - pushManager.fullSyncFlow.test { - pushManager.onMessageReceived(SYNC_SETTINGS_NOTIFICATION_MAP) - expectNoEvents() - } - } - - @Test - fun `onMessageReceived with sync vault does nothing`() = runTest { - pushManager.fullSyncFlow.test { - pushManager.onMessageReceived(SYNC_VAULT_NOTIFICATION_MAP) - expectNoEvents() - } - } - } - @Nested inner class NonNullUserState { @BeforeEach diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt index 3a4515897e..8d8d96e317 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt @@ -29,6 +29,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager @@ -91,6 +92,7 @@ class CipherManagerTest { coEvery { delete(*anyVararg()) } just runs } private val fakeAuthDiskSource = FakeAuthDiskSource() + private val fakeSettingsDiskSource = FakeSettingsDiskSource() private val ciphersService: CiphersService = mockk() private val vaultDiskSource: VaultDiskSource = mockk() private val vaultSdkSource: VaultSdkSource = mockk() @@ -106,6 +108,7 @@ class CipherManagerTest { private val cipherManager: CipherManager = CipherManagerImpl( ciphersService = ciphersService, + settingsDiskSource = fakeSettingsDiskSource, vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, authDiskSource = fakeAuthDiskSource, @@ -2403,6 +2406,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = false, @@ -2450,6 +2454,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = false, @@ -2492,6 +2497,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = true, @@ -2518,6 +2524,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = true, @@ -2552,6 +2559,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock).minus(5, ChronoUnit.MINUTES), isUpdate = true, @@ -2589,6 +2597,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = true, @@ -2620,6 +2629,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = false, @@ -2659,6 +2669,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = false, @@ -2697,6 +2708,7 @@ class CipherManagerTest { mutableSyncCipherUpsertFlow.tryEmit( SyncCipherUpsertData( + userId = userId, cipherId = cipherId, revisionDate = ZonedDateTime.now(clock), isUpdate = true, @@ -2712,6 +2724,43 @@ class CipherManagerTest { } } + @Test + fun `syncCipherUpsertFlow with inactive userId should clear the last sync time`() = runTest { + val number = 1 + val userId = "nonActiveUserId" + val cipherId = "mockId-$number" + val originalCipher = mockk { + every { revisionDate } returns ZonedDateTime.now(clock).minus(5, ChronoUnit.MINUTES) + } + val lastSyncTime = clock.instant() + + fakeSettingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = lastSyncTime) + fakeAuthDiskSource.userState = MOCK_USER_STATE + coEvery { + vaultDiskSource.getCipher(userId = userId, cipherId = cipherId) + } returns originalCipher + + mutableSyncCipherUpsertFlow.tryEmit( + SyncCipherUpsertData( + userId = userId, + cipherId = cipherId, + revisionDate = ZonedDateTime.now(clock), + isUpdate = true, + collectionIds = null, + organizationId = null, + ), + ) + + fakeSettingsDiskSource.assertLastSyncTime(userId = userId, expected = null) + coVerify(exactly = 1) { + vaultDiskSource.getCipher(userId = userId, cipherId = cipherId) + } + coVerify(exactly = 0) { + ciphersService.getCipher(cipherId) + vaultDiskSource.saveCipher(userId = userId, cipher = any()) + } + } + private fun setupMockUri( url: String, queryParams: Map = emptyMap(), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerTest.kt index 7cc4942639..4fb950034e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/FolderManagerTest.kt @@ -16,6 +16,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData @@ -49,6 +50,7 @@ import java.time.temporal.ChronoUnit class FolderManagerTest { private val fakeAuthDiskSource = FakeAuthDiskSource() + private val fakeSettingsDiskSource = FakeSettingsDiskSource() private val folderService = mockk() private val vaultDiskSource = mockk() private val vaultSdkSource = mockk() @@ -61,6 +63,7 @@ class FolderManagerTest { private val folderManager: FolderManager = FolderManagerImpl( authDiskSource = fakeAuthDiskSource, + settingsDiskSource = fakeSettingsDiskSource, folderService = folderService, vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, @@ -435,6 +438,7 @@ class FolderManagerTest { mutableSyncFolderUpsertFlow.tryEmit( SyncFolderUpsertData( + userId = userId, folderId = folderId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = false, @@ -460,6 +464,7 @@ class FolderManagerTest { mutableSyncFolderUpsertFlow.tryEmit( SyncFolderUpsertData( + userId = userId, folderId = folderId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = true, @@ -486,6 +491,7 @@ class FolderManagerTest { mutableSyncFolderUpsertFlow.tryEmit( SyncFolderUpsertData( + userId = userId, folderId = folderId, revisionDate = ZonedDateTime.ofInstant( Instant.ofEpochSecond(0), ZoneId.of("UTC"), @@ -518,6 +524,7 @@ class FolderManagerTest { mutableSyncFolderUpsertFlow.tryEmit( SyncFolderUpsertData( + userId = userId, folderId = folderId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = false, @@ -552,6 +559,7 @@ class FolderManagerTest { mutableSyncFolderUpsertFlow.tryEmit( SyncFolderUpsertData( + userId = userId, folderId = folderId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = true, @@ -563,6 +571,42 @@ class FolderManagerTest { vaultDiskSource.saveFolder(userId = userId, folder = folder) } } + + @Test + fun `syncFolderUpsertFlow with inactive userId should clear the last sync time`() = runTest { + val number = 1 + val userId = "nonActiveUserId" + val folderId = "mockId-$number" + val lastSyncTime = FIXED_CLOCK.instant() + + fakeSettingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = lastSyncTime) + fakeAuthDiskSource.userState = MOCK_USER_STATE + val folderView = createMockFolder( + number = number, + revisionDate = ZonedDateTime.now(FIXED_CLOCK).minus(5, ChronoUnit.MINUTES), + ) + coEvery { + vaultDiskSource.getFolders(userId = userId) + } returns MutableStateFlow(listOf(folderView)) + + mutableSyncFolderUpsertFlow.tryEmit( + SyncFolderUpsertData( + userId = userId, + folderId = folderId, + revisionDate = ZonedDateTime.now(FIXED_CLOCK), + isUpdate = true, + ), + ) + + fakeSettingsDiskSource.assertLastSyncTime(userId = userId, expected = null) + coVerify(exactly = 1) { + vaultDiskSource.getFolders(userId = userId) + } + coVerify(exactly = 0) { + folderService.getFolder(folderId = folderId) + vaultDiskSource.saveFolder(userId = userId, folder = any()) + } + } } private val FIXED_CLOCK: Clock = Clock.fixed( diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerTest.kt index 38ce6a5393..70f256f12d 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerTest.kt @@ -20,6 +20,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource +import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource import com.x8bit.bitwarden.data.platform.error.NoActiveUserException import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager @@ -64,6 +65,7 @@ class SendManagerTest { coEvery { delete(files = anyVararg()) } just runs } private val fakeAuthDiskSource = FakeAuthDiskSource() + private val fakeSettingsDiskSource = FakeSettingsDiskSource() private val sendsService = mockk() private val vaultDiskSource = mockk() private val vaultSdkSource = mockk() @@ -82,6 +84,7 @@ class SendManagerTest { vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, authDiskSource = fakeAuthDiskSource, + settingsDiskSource = fakeSettingsDiskSource, fileManager = fileManager, reviewPromptManager = reviewPromptManager, pushManager = pushManager, @@ -129,6 +132,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = false, @@ -152,6 +156,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = true, @@ -182,6 +187,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK).minus(5, ChronoUnit.MINUTES), isUpdate = true, @@ -221,6 +227,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = true, @@ -251,6 +258,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = false, @@ -283,6 +291,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = false, @@ -318,6 +327,7 @@ class SendManagerTest { mutableSyncSendUpsertFlow.tryEmit( SyncSendUpsertData( + userId = userId, sendId = sendId, revisionDate = ZonedDateTime.now(FIXED_CLOCK), isUpdate = true, @@ -330,6 +340,42 @@ class SendManagerTest { } } + @Test + fun `syncSendUpsertFlow with inactive userId should clear the last sync time`() = runTest { + val number = 1 + val userId = "nonActiveUserId" + val sendId = "mockId-$number" + val lastSyncTime = FIXED_CLOCK.instant() + + fakeSettingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = lastSyncTime) + fakeAuthDiskSource.userState = MOCK_USER_STATE + val sendView = createMockSend( + number = number, + revisionDate = ZonedDateTime.now(FIXED_CLOCK).minus(5, ChronoUnit.MINUTES), + ) + coEvery { + vaultDiskSource.getSends(userId = userId) + } returns MutableStateFlow(listOf(sendView)) + + mutableSyncSendUpsertFlow.tryEmit( + SyncSendUpsertData( + userId = userId, + sendId = sendId, + revisionDate = ZonedDateTime.now(FIXED_CLOCK), + isUpdate = true, + ), + ) + + fakeSettingsDiskSource.assertLastSyncTime(userId = userId, expected = null) + coVerify(exactly = 1) { + vaultDiskSource.getSends(userId = userId) + } + coVerify(exactly = 0) { + sendsService.getSend(sendId = sendId) + vaultDiskSource.saveSend(userId = userId, send = any()) + } + } + @Test fun `createSend with no active user should return CreateSendResult Error`() = runTest {