mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 20:07:59 -06:00
Refactor Folder logic into FolderManager (#5904)
This commit is contained in:
parent
4f244c52fa
commit
d53f3f313c
@ -0,0 +1,26 @@
|
|||||||
|
package com.x8bit.bitwarden.data.vault.manager
|
||||||
|
|
||||||
|
import com.bitwarden.vault.FolderView
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the creating, updating, and deleting folders.
|
||||||
|
*/
|
||||||
|
interface FolderManager {
|
||||||
|
/**
|
||||||
|
* Attempt to create a folder.
|
||||||
|
*/
|
||||||
|
suspend fun createFolder(folderView: FolderView): CreateFolderResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to delete a folder.
|
||||||
|
*/
|
||||||
|
suspend fun deleteFolder(folderId: String): DeleteFolderResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to update a folder.
|
||||||
|
*/
|
||||||
|
suspend fun updateFolder(folderId: String, folderView: FolderView): UpdateFolderResult
|
||||||
|
}
|
||||||
@ -0,0 +1,170 @@
|
|||||||
|
package com.x8bit.bitwarden.data.vault.manager
|
||||||
|
|
||||||
|
import com.bitwarden.core.data.util.flatMap
|
||||||
|
import com.bitwarden.data.manager.DispatcherManager
|
||||||
|
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.error.NoActiveUserException
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
||||||
|
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||||
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkFolder
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of the [FolderManager].
|
||||||
|
*/
|
||||||
|
class FolderManagerImpl(
|
||||||
|
private val authDiskSource: AuthDiskSource,
|
||||||
|
private val folderService: FolderService,
|
||||||
|
private val vaultDiskSource: VaultDiskSource,
|
||||||
|
private val vaultSdkSource: VaultSdkSource,
|
||||||
|
dispatcherManager: DispatcherManager,
|
||||||
|
pushManager: PushManager,
|
||||||
|
) : FolderManager {
|
||||||
|
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||||
|
private val ioScope = CoroutineScope(dispatcherManager.io)
|
||||||
|
private val activeUserId: String? get() = authDiskSource.userState?.activeUserId
|
||||||
|
|
||||||
|
init {
|
||||||
|
pushManager
|
||||||
|
.syncFolderDeleteFlow
|
||||||
|
.onEach(::deleteFolder)
|
||||||
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
|
pushManager
|
||||||
|
.syncFolderUpsertFlow
|
||||||
|
.onEach(::syncFolderIfNecessary)
|
||||||
|
.launchIn(ioScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createFolder(folderView: FolderView): CreateFolderResult {
|
||||||
|
val userId = activeUserId ?: return CreateFolderResult.Error(NoActiveUserException())
|
||||||
|
return vaultSdkSource
|
||||||
|
.encryptFolder(userId = userId, folder = folderView)
|
||||||
|
.flatMap { folderService.createFolder(body = it.toEncryptedNetworkFolder()) }
|
||||||
|
.onSuccess { vaultDiskSource.saveFolder(userId = userId, folder = it) }
|
||||||
|
.flatMap {
|
||||||
|
vaultSdkSource.decryptFolder(userId = userId, folder = it.toEncryptedSdkFolder())
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
onSuccess = { CreateFolderResult.Success(folderView = it) },
|
||||||
|
onFailure = { CreateFolderResult.Error(error = it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteFolder(folderId: String): DeleteFolderResult {
|
||||||
|
val userId = activeUserId ?: return DeleteFolderResult.Error(NoActiveUserException())
|
||||||
|
return folderService
|
||||||
|
.deleteFolder(folderId = folderId)
|
||||||
|
.onSuccess {
|
||||||
|
clearFolderIdFromCiphers(userId = userId, folderId = folderId)
|
||||||
|
vaultDiskSource.deleteFolder(userId = userId, folderId = folderId)
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
onSuccess = { DeleteFolderResult.Success },
|
||||||
|
onFailure = { DeleteFolderResult.Error(error = it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateFolder(
|
||||||
|
folderId: String,
|
||||||
|
folderView: FolderView,
|
||||||
|
): UpdateFolderResult {
|
||||||
|
val userId = activeUserId ?: return UpdateFolderResult.Error(
|
||||||
|
errorMessage = null,
|
||||||
|
error = NoActiveUserException(),
|
||||||
|
)
|
||||||
|
return vaultSdkSource
|
||||||
|
.encryptFolder(userId = userId, folder = folderView)
|
||||||
|
.flatMap { folder ->
|
||||||
|
folderService.updateFolder(
|
||||||
|
folderId = folder.id.toString(),
|
||||||
|
body = folder.toEncryptedNetworkFolder(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
onSuccess = { response ->
|
||||||
|
when (response) {
|
||||||
|
is UpdateFolderResponseJson.Success -> {
|
||||||
|
vaultDiskSource.saveFolder(userId = userId, folder = response.folder)
|
||||||
|
vaultSdkSource
|
||||||
|
.decryptFolder(
|
||||||
|
userId = userId,
|
||||||
|
folder = response.folder.toEncryptedSdkFolder(),
|
||||||
|
)
|
||||||
|
.fold(
|
||||||
|
onSuccess = { UpdateFolderResult.Success(it) },
|
||||||
|
onFailure = {
|
||||||
|
UpdateFolderResult.Error(errorMessage = null, error = it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is UpdateFolderResponseJson.Invalid -> {
|
||||||
|
UpdateFolderResult.Error(errorMessage = response.message, error = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { UpdateFolderResult.Error(it.message, error = it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun clearFolderIdFromCiphers(userId: String, folderId: String) {
|
||||||
|
vaultDiskSource.getCiphers(userId = userId).forEach {
|
||||||
|
if (it.folderId == folderId) {
|
||||||
|
vaultDiskSource.saveCipher(userId = userId, cipher = it.copy(folderId = null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the folder specified by [syncFolderDeleteData] from disk.
|
||||||
|
*/
|
||||||
|
private suspend fun deleteFolder(syncFolderDeleteData: SyncFolderDeleteData) {
|
||||||
|
clearFolderIdFromCiphers(
|
||||||
|
folderId = syncFolderDeleteData.folderId,
|
||||||
|
userId = syncFolderDeleteData.userId,
|
||||||
|
)
|
||||||
|
vaultDiskSource.deleteFolder(
|
||||||
|
folderId = syncFolderDeleteData.folderId,
|
||||||
|
userId = syncFolderDeleteData.userId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs an individual folder contained in [syncFolderUpsertData] to disk if certain criteria
|
||||||
|
* are met.
|
||||||
|
*/
|
||||||
|
private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) {
|
||||||
|
val userId = activeUserId ?: return
|
||||||
|
val folderId = syncFolderUpsertData.folderId
|
||||||
|
val isUpdate = syncFolderUpsertData.isUpdate
|
||||||
|
val revisionDate = syncFolderUpsertData.revisionDate
|
||||||
|
val localFolder = vaultDiskSource
|
||||||
|
.getFolders(userId = userId)
|
||||||
|
.first()
|
||||||
|
.find { it.id == folderId }
|
||||||
|
val isValidCreate = !isUpdate && localFolder == null
|
||||||
|
val isValidUpdate = isUpdate &&
|
||||||
|
localFolder != null &&
|
||||||
|
localFolder.revisionDate.toEpochSecond() < revisionDate.toEpochSecond()
|
||||||
|
|
||||||
|
if (!isValidCreate && !isValidUpdate) return
|
||||||
|
|
||||||
|
folderService
|
||||||
|
.getFolder(folderId = folderId)
|
||||||
|
.onSuccess { vaultDiskSource.saveFolder(userId = userId, folder = it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,8 +5,9 @@ import com.bitwarden.core.data.manager.realtime.RealtimeManager
|
|||||||
import com.bitwarden.data.manager.DispatcherManager
|
import com.bitwarden.data.manager.DispatcherManager
|
||||||
import com.bitwarden.network.service.CiphersService
|
import com.bitwarden.network.service.CiphersService
|
||||||
import com.bitwarden.network.service.DownloadService
|
import com.bitwarden.network.service.DownloadService
|
||||||
import com.bitwarden.network.service.SyncService
|
import com.bitwarden.network.service.FolderService
|
||||||
import com.bitwarden.network.service.SendsService
|
import com.bitwarden.network.service.SendsService
|
||||||
|
import com.bitwarden.network.service.SyncService
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||||
import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager
|
import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager
|
||||||
@ -24,6 +25,8 @@ import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManager
|
|||||||
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManagerImpl
|
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManagerImpl
|
||||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.FileManagerImpl
|
import com.x8bit.bitwarden.data.vault.manager.FileManagerImpl
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.FolderManager
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.FolderManagerImpl
|
||||||
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.SendManagerImpl
|
import com.x8bit.bitwarden.data.vault.manager.SendManagerImpl
|
||||||
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
||||||
@ -71,6 +74,24 @@ object VaultManagerModule {
|
|||||||
pushManager = pushManager,
|
pushManager = pushManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFolderManager(
|
||||||
|
folderService: FolderService,
|
||||||
|
vaultDiskSource: VaultDiskSource,
|
||||||
|
vaultSdkSource: VaultSdkSource,
|
||||||
|
authDiskSource: AuthDiskSource,
|
||||||
|
dispatcherManager: DispatcherManager,
|
||||||
|
pushManager: PushManager,
|
||||||
|
): FolderManager = FolderManagerImpl(
|
||||||
|
authDiskSource = authDiskSource,
|
||||||
|
folderService = folderService,
|
||||||
|
vaultDiskSource = vaultDiskSource,
|
||||||
|
vaultSdkSource = vaultSdkSource,
|
||||||
|
dispatcherManager = dispatcherManager,
|
||||||
|
pushManager = pushManager,
|
||||||
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSendManager(
|
fun provideSendManager(
|
||||||
|
|||||||
@ -13,19 +13,17 @@ import com.bitwarden.vault.CipherView
|
|||||||
import com.bitwarden.vault.DecryptCipherListResult
|
import com.bitwarden.vault.DecryptCipherListResult
|
||||||
import com.bitwarden.vault.FolderView
|
import com.bitwarden.vault.FolderView
|
||||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.FolderManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.SyncVaultDataResult
|
import com.x8bit.bitwarden.data.vault.manager.model.SyncVaultDataResult
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult
|
import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
@ -37,7 +35,7 @@ import javax.crypto.Cipher
|
|||||||
* Responsible for managing vault data inside the network layer.
|
* Responsible for managing vault data inside the network layer.
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
interface VaultRepository : CipherManager, SendManager, VaultLockManager {
|
interface VaultRepository : CipherManager, FolderManager, SendManager, VaultLockManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [VaultFilterType] for the current user.
|
* The [VaultFilterType] for the current user.
|
||||||
@ -204,21 +202,6 @@ interface VaultRepository : CipherManager, SendManager, VaultLockManager {
|
|||||||
*/
|
*/
|
||||||
suspend fun generateTotp(cipherId: String, time: DateTime): GenerateTotpResult
|
suspend fun generateTotp(cipherId: String, time: DateTime): GenerateTotpResult
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to create a folder.
|
|
||||||
*/
|
|
||||||
suspend fun createFolder(folderView: FolderView): CreateFolderResult
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to delete a folder.
|
|
||||||
*/
|
|
||||||
suspend fun deleteFolder(folderId: String): DeleteFolderResult
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to update a folder.
|
|
||||||
*/
|
|
||||||
suspend fun updateFolder(folderId: String, folderView: FolderView): UpdateFolderResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to get the user's vault data for export.
|
* Attempt to get the user's vault data for export.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -10,12 +10,9 @@ import com.bitwarden.core.data.repository.util.map
|
|||||||
import com.bitwarden.core.data.repository.util.mapNullable
|
import com.bitwarden.core.data.repository.util.mapNullable
|
||||||
import com.bitwarden.core.data.repository.util.updateToPendingOrLoading
|
import com.bitwarden.core.data.repository.util.updateToPendingOrLoading
|
||||||
import com.bitwarden.core.data.util.asFailure
|
import com.bitwarden.core.data.util.asFailure
|
||||||
import com.bitwarden.core.data.util.flatMap
|
|
||||||
import com.bitwarden.data.manager.DispatcherManager
|
import com.bitwarden.data.manager.DispatcherManager
|
||||||
import com.bitwarden.exporters.ExportFormat
|
import com.bitwarden.exporters.ExportFormat
|
||||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||||
import com.bitwarden.network.model.UpdateFolderResponseJson
|
|
||||||
import com.bitwarden.network.service.FolderService
|
|
||||||
import com.bitwarden.network.util.isNoConnectionError
|
import com.bitwarden.network.util.isNoConnectionError
|
||||||
import com.bitwarden.sdk.Fido2CredentialStore
|
import com.bitwarden.sdk.Fido2CredentialStore
|
||||||
import com.bitwarden.send.SendView
|
import com.bitwarden.send.SendView
|
||||||
@ -34,14 +31,13 @@ import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
|
|||||||
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
|
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
|
||||||
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn
|
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndUnlocked
|
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndUnlocked
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManager
|
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManager
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.FolderManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||||
@ -50,21 +46,17 @@ import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult
|
|||||||
import com.x8bit.bitwarden.data.vault.manager.model.ImportCxfPayloadResult
|
import com.x8bit.bitwarden.data.vault.manager.model.ImportCxfPayloadResult
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.SyncVaultDataResult
|
import com.x8bit.bitwarden.data.vault.manager.model.SyncVaultDataResult
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult
|
import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabetically
|
import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabetically
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabeticallyByTypeAndOrganization
|
import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabeticallyByTypeAndOrganization
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
|
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkFolder
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
|
||||||
@ -87,7 +79,6 @@ import kotlinx.coroutines.flow.asSharedFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
@ -116,12 +107,12 @@ private const val STOP_TIMEOUT_DELAY_MS: Long = 1000L
|
|||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions", "LongParameterList", "LargeClass")
|
@Suppress("TooManyFunctions", "LongParameterList", "LargeClass")
|
||||||
class VaultRepositoryImpl(
|
class VaultRepositoryImpl(
|
||||||
private val folderService: FolderService,
|
|
||||||
private val vaultDiskSource: VaultDiskSource,
|
private val vaultDiskSource: VaultDiskSource,
|
||||||
private val vaultSdkSource: VaultSdkSource,
|
private val vaultSdkSource: VaultSdkSource,
|
||||||
private val authDiskSource: AuthDiskSource,
|
private val authDiskSource: AuthDiskSource,
|
||||||
private val settingsDiskSource: SettingsDiskSource,
|
private val settingsDiskSource: SettingsDiskSource,
|
||||||
private val cipherManager: CipherManager,
|
private val cipherManager: CipherManager,
|
||||||
|
private val folderManager: FolderManager,
|
||||||
private val sendManager: SendManager,
|
private val sendManager: SendManager,
|
||||||
private val vaultLockManager: VaultLockManager,
|
private val vaultLockManager: VaultLockManager,
|
||||||
private val totpCodeManager: TotpCodeManager,
|
private val totpCodeManager: TotpCodeManager,
|
||||||
@ -133,6 +124,7 @@ class VaultRepositoryImpl(
|
|||||||
private val credentialExchangeImportManager: CredentialExchangeImportManager,
|
private val credentialExchangeImportManager: CredentialExchangeImportManager,
|
||||||
) : VaultRepository,
|
) : VaultRepository,
|
||||||
CipherManager by cipherManager,
|
CipherManager by cipherManager,
|
||||||
|
FolderManager by folderManager,
|
||||||
SendManager by sendManager,
|
SendManager by sendManager,
|
||||||
VaultLockManager by vaultLockManager {
|
VaultLockManager by vaultLockManager {
|
||||||
|
|
||||||
@ -281,16 +273,6 @@ class VaultRepositoryImpl(
|
|||||||
.onEach { sync(forced = false) }
|
.onEach { sync(forced = false) }
|
||||||
.launchIn(unconfinedScope)
|
.launchIn(unconfinedScope)
|
||||||
|
|
||||||
pushManager
|
|
||||||
.syncFolderDeleteFlow
|
|
||||||
.onEach(::deleteFolder)
|
|
||||||
.launchIn(unconfinedScope)
|
|
||||||
|
|
||||||
pushManager
|
|
||||||
.syncFolderUpsertFlow
|
|
||||||
.onEach(::syncFolderIfNecessary)
|
|
||||||
.launchIn(ioScope)
|
|
||||||
|
|
||||||
databaseSchemeManager
|
databaseSchemeManager
|
||||||
.databaseSchemeChangeFlow
|
.databaseSchemeChangeFlow
|
||||||
.onEach { sync(forced = true) }
|
.onEach { sync(forced = true) }
|
||||||
@ -645,106 +627,6 @@ class VaultRepositoryImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createFolder(folderView: FolderView): CreateFolderResult {
|
|
||||||
val userId = activeUserId
|
|
||||||
?: return CreateFolderResult.Error(error = NoActiveUserException())
|
|
||||||
return vaultSdkSource
|
|
||||||
.encryptFolder(
|
|
||||||
userId = userId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
.flatMap { folder ->
|
|
||||||
folderService
|
|
||||||
.createFolder(
|
|
||||||
body = folder.toEncryptedNetworkFolder(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.onSuccess { vaultDiskSource.saveFolder(userId = userId, folder = it) }
|
|
||||||
.flatMap { vaultSdkSource.decryptFolder(userId, it.toEncryptedSdkFolder()) }
|
|
||||||
.fold(
|
|
||||||
onSuccess = { CreateFolderResult.Success(folderView = it) },
|
|
||||||
onFailure = { CreateFolderResult.Error(error = it) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateFolder(
|
|
||||||
folderId: String,
|
|
||||||
folderView: FolderView,
|
|
||||||
): UpdateFolderResult {
|
|
||||||
val userId = activeUserId ?: return UpdateFolderResult.Error(
|
|
||||||
errorMessage = null,
|
|
||||||
error = NoActiveUserException(),
|
|
||||||
)
|
|
||||||
return vaultSdkSource
|
|
||||||
.encryptFolder(
|
|
||||||
userId = userId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
.flatMap { folder ->
|
|
||||||
folderService
|
|
||||||
.updateFolder(
|
|
||||||
folderId = folder.id.toString(),
|
|
||||||
body = folder.toEncryptedNetworkFolder(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.fold(
|
|
||||||
onSuccess = { response ->
|
|
||||||
when (response) {
|
|
||||||
is UpdateFolderResponseJson.Success -> {
|
|
||||||
vaultDiskSource.saveFolder(userId, response.folder)
|
|
||||||
vaultSdkSource
|
|
||||||
.decryptFolder(
|
|
||||||
userId,
|
|
||||||
response.folder.toEncryptedSdkFolder(),
|
|
||||||
)
|
|
||||||
.fold(
|
|
||||||
onSuccess = { UpdateFolderResult.Success(it) },
|
|
||||||
onFailure = {
|
|
||||||
UpdateFolderResult.Error(
|
|
||||||
errorMessage = null,
|
|
||||||
error = it,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is UpdateFolderResponseJson.Invalid -> {
|
|
||||||
UpdateFolderResult.Error(
|
|
||||||
errorMessage = response.message,
|
|
||||||
error = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFailure = { UpdateFolderResult.Error(it.message, error = it) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteFolder(folderId: String): DeleteFolderResult {
|
|
||||||
val userId = activeUserId
|
|
||||||
?: return DeleteFolderResult.Error(error = NoActiveUserException())
|
|
||||||
return folderService
|
|
||||||
.deleteFolder(
|
|
||||||
folderId = folderId,
|
|
||||||
)
|
|
||||||
.onSuccess {
|
|
||||||
clearFolderIdFromCiphers(folderId, userId)
|
|
||||||
vaultDiskSource.deleteFolder(userId, folderId)
|
|
||||||
}
|
|
||||||
.fold(
|
|
||||||
onSuccess = { DeleteFolderResult.Success },
|
|
||||||
onFailure = { DeleteFolderResult.Error(error = it) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun clearFolderIdFromCiphers(folderId: String, userId: String) {
|
|
||||||
vaultDiskSource.getCiphers(userId = userId).forEach {
|
|
||||||
if (it.folderId == folderId) {
|
|
||||||
vaultDiskSource.saveCipher(userId = userId, cipher = it.copy(folderId = null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun exportVaultDataToString(
|
override suspend fun exportVaultDataToString(
|
||||||
format: ExportFormat,
|
format: ExportFormat,
|
||||||
restrictedTypes: List<CipherType>,
|
restrictedTypes: List<CipherType>,
|
||||||
@ -1049,48 +931,6 @@ class VaultRepositoryImpl(
|
|||||||
}
|
}
|
||||||
?: DataState.Loading
|
?: DataState.Loading
|
||||||
|
|
||||||
//region Push notification helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the folder specified by [syncFolderDeleteData] from disk.
|
|
||||||
*/
|
|
||||||
private suspend fun deleteFolder(syncFolderDeleteData: SyncFolderDeleteData) {
|
|
||||||
clearFolderIdFromCiphers(
|
|
||||||
folderId = syncFolderDeleteData.folderId,
|
|
||||||
userId = syncFolderDeleteData.userId,
|
|
||||||
)
|
|
||||||
vaultDiskSource.deleteFolder(
|
|
||||||
folderId = syncFolderDeleteData.folderId,
|
|
||||||
userId = syncFolderDeleteData.userId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Syncs an individual folder contained in [syncFolderUpsertData] to disk if certain criteria
|
|
||||||
* are met.
|
|
||||||
*/
|
|
||||||
private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) {
|
|
||||||
val userId = activeUserId ?: return
|
|
||||||
val folderId = syncFolderUpsertData.folderId
|
|
||||||
val isUpdate = syncFolderUpsertData.isUpdate
|
|
||||||
val revisionDate = syncFolderUpsertData.revisionDate
|
|
||||||
val localFolder = vaultDiskSource
|
|
||||||
.getFolders(userId = userId)
|
|
||||||
.first()
|
|
||||||
.find { it.id == folderId }
|
|
||||||
val isValidCreate = !isUpdate && localFolder == null
|
|
||||||
val isValidUpdate = isUpdate &&
|
|
||||||
localFolder != null &&
|
|
||||||
localFolder.revisionDate.toEpochSecond() < revisionDate.toEpochSecond()
|
|
||||||
|
|
||||||
if (!isValidCreate && !isValidUpdate) return
|
|
||||||
|
|
||||||
folderService
|
|
||||||
.getFolder(folderId = folderId)
|
|
||||||
.onSuccess { vaultDiskSource.saveFolder(userId = userId, folder = it) }
|
|
||||||
}
|
|
||||||
//endregion Push Notification helpers
|
|
||||||
|
|
||||||
private suspend fun syncInternal(
|
private suspend fun syncInternal(
|
||||||
userId: String,
|
userId: String,
|
||||||
forced: Boolean,
|
forced: Boolean,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package com.x8bit.bitwarden.data.vault.repository.di
|
package com.x8bit.bitwarden.data.vault.repository.di
|
||||||
|
|
||||||
import com.bitwarden.data.manager.DispatcherManager
|
import com.bitwarden.data.manager.DispatcherManager
|
||||||
import com.bitwarden.network.service.FolderService
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
||||||
@ -10,6 +9,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
|||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManager
|
import com.x8bit.bitwarden.data.vault.manager.CredentialExchangeImportManager
|
||||||
|
import com.x8bit.bitwarden.data.vault.manager.FolderManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
import com.x8bit.bitwarden.data.vault.manager.SendManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
||||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||||
@ -33,12 +33,12 @@ object VaultRepositoryModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesVaultRepository(
|
fun providesVaultRepository(
|
||||||
folderService: FolderService,
|
|
||||||
vaultDiskSource: VaultDiskSource,
|
vaultDiskSource: VaultDiskSource,
|
||||||
vaultSdkSource: VaultSdkSource,
|
vaultSdkSource: VaultSdkSource,
|
||||||
authDiskSource: AuthDiskSource,
|
authDiskSource: AuthDiskSource,
|
||||||
settingsDiskSource: SettingsDiskSource,
|
settingsDiskSource: SettingsDiskSource,
|
||||||
cipherManager: CipherManager,
|
cipherManager: CipherManager,
|
||||||
|
folderManager: FolderManager,
|
||||||
sendManager: SendManager,
|
sendManager: SendManager,
|
||||||
vaultLockManager: VaultLockManager,
|
vaultLockManager: VaultLockManager,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
@ -49,12 +49,12 @@ object VaultRepositoryModule {
|
|||||||
vaultSyncManager: VaultSyncManager,
|
vaultSyncManager: VaultSyncManager,
|
||||||
credentialExchangeImportManager: CredentialExchangeImportManager,
|
credentialExchangeImportManager: CredentialExchangeImportManager,
|
||||||
): VaultRepository = VaultRepositoryImpl(
|
): VaultRepository = VaultRepositoryImpl(
|
||||||
folderService = folderService,
|
|
||||||
vaultDiskSource = vaultDiskSource,
|
vaultDiskSource = vaultDiskSource,
|
||||||
vaultSdkSource = vaultSdkSource,
|
vaultSdkSource = vaultSdkSource,
|
||||||
authDiskSource = authDiskSource,
|
authDiskSource = authDiskSource,
|
||||||
settingsDiskSource = settingsDiskSource,
|
settingsDiskSource = settingsDiskSource,
|
||||||
cipherManager = cipherManager,
|
cipherManager = cipherManager,
|
||||||
|
folderManager = folderManager,
|
||||||
sendManager = sendManager,
|
sendManager = sendManager,
|
||||||
vaultLockManager = vaultLockManager,
|
vaultLockManager = vaultLockManager,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
|
|||||||
@ -0,0 +1,610 @@
|
|||||||
|
package com.x8bit.bitwarden.data.vault.manager
|
||||||
|
|
||||||
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.bitwarden.core.data.util.asFailure
|
||||||
|
import com.bitwarden.core.data.util.asSuccess
|
||||||
|
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
|
||||||
|
import com.bitwarden.network.model.FolderJsonRequest
|
||||||
|
import com.bitwarden.network.model.SyncResponseJson
|
||||||
|
import com.bitwarden.network.model.UpdateFolderResponseJson
|
||||||
|
import com.bitwarden.network.model.createMockCipher
|
||||||
|
import com.bitwarden.network.model.createMockFolder
|
||||||
|
import com.bitwarden.network.service.FolderService
|
||||||
|
import com.bitwarden.vault.Folder
|
||||||
|
import com.bitwarden.vault.FolderView
|
||||||
|
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.error.NoActiveUserException
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
||||||
|
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||||
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
||||||
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolder
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkConstructor
|
||||||
|
import io.mockk.runs
|
||||||
|
import io.mockk.unmockkConstructor
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
|
class FolderManagerTest {
|
||||||
|
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||||
|
private val folderService = mockk<FolderService>()
|
||||||
|
private val vaultDiskSource = mockk<VaultDiskSource>()
|
||||||
|
private val vaultSdkSource = mockk<VaultSdkSource>()
|
||||||
|
private val mutableSyncFolderDeleteFlow = bufferedMutableSharedFlow<SyncFolderDeleteData>()
|
||||||
|
private val mutableSyncFolderUpsertFlow = bufferedMutableSharedFlow<SyncFolderUpsertData>()
|
||||||
|
private val pushManager: PushManager = mockk {
|
||||||
|
every { syncFolderDeleteFlow } returns mutableSyncFolderDeleteFlow
|
||||||
|
every { syncFolderUpsertFlow } returns mutableSyncFolderUpsertFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
private val folderManager: FolderManager = FolderManagerImpl(
|
||||||
|
authDiskSource = fakeAuthDiskSource,
|
||||||
|
folderService = folderService,
|
||||||
|
vaultDiskSource = vaultDiskSource,
|
||||||
|
vaultSdkSource = vaultSdkSource,
|
||||||
|
pushManager = pushManager,
|
||||||
|
dispatcherManager = FakeDispatcherManager(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
mockkConstructor(NoActiveUserException::class)
|
||||||
|
every {
|
||||||
|
anyConstructed<NoActiveUserException>() == any<NoActiveUserException>()
|
||||||
|
} returns true
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkConstructor(NoActiveUserException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `createFolder with no active user should return CreateFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = null
|
||||||
|
|
||||||
|
val result = folderManager.createFolder(folderView = mockk())
|
||||||
|
|
||||||
|
assertEquals(CreateFolderResult.Error(error = NoActiveUserException()), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `createFolder with folderService Delete failure should return DeleteFolderResult Failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderId = "mockId-1"
|
||||||
|
val error = Throwable("fail")
|
||||||
|
coEvery { folderService.deleteFolder(folderId = folderId) } returns error.asFailure()
|
||||||
|
|
||||||
|
val result = folderManager.deleteFolder(folderId = folderId)
|
||||||
|
|
||||||
|
assertEquals(DeleteFolderResult.Error(error = error), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `createFolder with encryptFolder failure should return CreateFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = null,
|
||||||
|
name = "TestName",
|
||||||
|
revisionDate = Instant.now(FIXED_CLOCK),
|
||||||
|
)
|
||||||
|
val error = IllegalStateException()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns error.asFailure()
|
||||||
|
|
||||||
|
val result = folderManager.createFolder(folderView = folderView)
|
||||||
|
assertEquals(CreateFolderResult.Error(error = error), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `createFolder with folderService failure should return CreateFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
val date = Instant.now(FIXED_CLOCK)
|
||||||
|
val testFolderName = "TestName"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = null,
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = date,
|
||||||
|
)
|
||||||
|
val error = IllegalStateException()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns Folder(id = null, name = testFolderName, revisionDate = date).asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
folderService.createFolder(body = FolderJsonRequest(name = testFolderName))
|
||||||
|
} returns error.asFailure()
|
||||||
|
|
||||||
|
val result = folderManager.createFolder(folderView = folderView)
|
||||||
|
assertEquals(CreateFolderResult.Error(error = error), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `createFolder with folderService createFolder should return CreateFolderResult success`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val date = Instant.now(FIXED_CLOCK)
|
||||||
|
val testFolderName = "TestName"
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = null,
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = date,
|
||||||
|
)
|
||||||
|
val networkFolder = SyncResponseJson.Folder(
|
||||||
|
id = "1",
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns Folder(id = null, name = testFolderName, revisionDate = date).asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
folderService.createFolder(body = FolderJsonRequest(name = testFolderName))
|
||||||
|
} returns networkFolder.asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.saveFolder(userId = ACTIVE_USER_ID, folder = networkFolder)
|
||||||
|
} just runs
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.decryptFolder(
|
||||||
|
userId = ACTIVE_USER_ID,
|
||||||
|
folder = networkFolder.toEncryptedSdkFolder(),
|
||||||
|
)
|
||||||
|
} returns folderView.asSuccess()
|
||||||
|
|
||||||
|
val result = folderManager.createFolder(folderView = folderView)
|
||||||
|
|
||||||
|
assertEquals(CreateFolderResult.Success(folderView = folderView), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `deleteFolder with no active user should return DeleteFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = null
|
||||||
|
|
||||||
|
val result = folderManager.deleteFolder(folderId = "Test")
|
||||||
|
|
||||||
|
assertEquals(DeleteFolderResult.Error(error = NoActiveUserException()), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `DeleteFolder with folderService Delete failure should return DeleteFolderResult Failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val error = Throwable("fail")
|
||||||
|
val folderId = "mockId-1"
|
||||||
|
coEvery { folderService.deleteFolder(folderId = folderId) } returns error.asFailure()
|
||||||
|
|
||||||
|
val result = folderManager.deleteFolder(folderId = folderId)
|
||||||
|
|
||||||
|
assertEquals(DeleteFolderResult.Error(error = error), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `DeleteFolder with folderService Delete success should return DeleteFolderResult Success and update ciphers`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val userId = ACTIVE_USER_ID
|
||||||
|
val folderId = "mockFolderId-1"
|
||||||
|
val mockCipher = createMockCipher(number = 1)
|
||||||
|
val ciphers = listOf(mockCipher, createMockCipher(number = 2))
|
||||||
|
coEvery { folderService.deleteFolder(folderId = folderId) } returns Unit.asSuccess()
|
||||||
|
coEvery { vaultDiskSource.deleteFolder(userId = userId, folderId = folderId) } just runs
|
||||||
|
coEvery { vaultDiskSource.getCiphers(userId = userId) } returns ciphers
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.saveCipher(
|
||||||
|
userId = userId,
|
||||||
|
cipher = mockCipher.copy(folderId = null),
|
||||||
|
)
|
||||||
|
} just runs
|
||||||
|
|
||||||
|
val result = folderManager.deleteFolder(folderId = folderId)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
vaultDiskSource.saveCipher(
|
||||||
|
userId = userId,
|
||||||
|
cipher = mockCipher.copy(folderId = null),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(DeleteFolderResult.Success, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `updateFolder with no active user should return UpdateFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = null
|
||||||
|
|
||||||
|
val result = folderManager.updateFolder(folderId = "Test", folderView = mockk())
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
UpdateFolderResult.Error(errorMessage = null, error = NoActiveUserException()),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `updateFolder with encryptFolder failure should return UpdateFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderId = "testId"
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = folderId,
|
||||||
|
name = "TestName",
|
||||||
|
revisionDate = Instant.now(FIXED_CLOCK),
|
||||||
|
)
|
||||||
|
val error = IllegalStateException()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns error.asFailure()
|
||||||
|
|
||||||
|
val result = folderManager.updateFolder(folderId = folderId, folderView = folderView)
|
||||||
|
|
||||||
|
assertEquals(UpdateFolderResult.Error(errorMessage = null, error = error), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `updateFolder with folderService failure should return UpdateFolderResult failure`() =
|
||||||
|
runTest {
|
||||||
|
val date = Instant.now(FIXED_CLOCK)
|
||||||
|
val testFolderName = "TestName"
|
||||||
|
val folderId = "testId"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = folderId,
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = date,
|
||||||
|
)
|
||||||
|
val error = IllegalStateException()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns Folder(id = folderId, name = testFolderName, revisionDate = date).asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
folderService.updateFolder(
|
||||||
|
folderId = folderId,
|
||||||
|
body = FolderJsonRequest(name = testFolderName),
|
||||||
|
)
|
||||||
|
} returns error.asFailure()
|
||||||
|
|
||||||
|
val result = folderManager.updateFolder(folderId = folderId, folderView = folderView)
|
||||||
|
assertEquals(UpdateFolderResult.Error(errorMessage = null, error = error), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `updateFolder with folderService updateFolder Invalid response should return UpdateFolderResult Error with a non-null message`() =
|
||||||
|
runTest {
|
||||||
|
val date = Instant.now(FIXED_CLOCK)
|
||||||
|
val testFolderName = "TestName"
|
||||||
|
val folderId = "testId"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = folderId,
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = date,
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns Folder(id = folderId, name = testFolderName, revisionDate = date).asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
folderService.updateFolder(
|
||||||
|
folderId = folderId,
|
||||||
|
body = FolderJsonRequest(name = testFolderName),
|
||||||
|
)
|
||||||
|
} returns UpdateFolderResponseJson
|
||||||
|
.Invalid(
|
||||||
|
message = "You do not have permission to edit this.",
|
||||||
|
validationErrors = null,
|
||||||
|
)
|
||||||
|
.asSuccess()
|
||||||
|
|
||||||
|
val result = folderManager.updateFolder(folderId = folderId, folderView = folderView)
|
||||||
|
assertEquals(
|
||||||
|
UpdateFolderResult.Error(
|
||||||
|
errorMessage = "You do not have permission to edit this.",
|
||||||
|
error = null,
|
||||||
|
),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `updateFolder with folderService updateFolder success should return UpdateFolderResult success`() =
|
||||||
|
runTest {
|
||||||
|
val date = Instant.now(FIXED_CLOCK)
|
||||||
|
val testFolderName = "TestName"
|
||||||
|
val folderId = "testId"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folderView = FolderView(
|
||||||
|
id = folderId,
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = date,
|
||||||
|
)
|
||||||
|
val networkFolder = SyncResponseJson.Folder(
|
||||||
|
id = "1",
|
||||||
|
name = testFolderName,
|
||||||
|
revisionDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.encryptFolder(userId = ACTIVE_USER_ID, folder = folderView)
|
||||||
|
} returns Folder(id = folderId, name = testFolderName, revisionDate = date).asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
folderService.updateFolder(
|
||||||
|
folderId = folderId,
|
||||||
|
body = FolderJsonRequest(name = testFolderName),
|
||||||
|
)
|
||||||
|
} returns UpdateFolderResponseJson
|
||||||
|
.Success(folder = networkFolder)
|
||||||
|
.asSuccess()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.saveFolder(userId = ACTIVE_USER_ID, folder = networkFolder)
|
||||||
|
} just runs
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
vaultSdkSource.decryptFolder(
|
||||||
|
userId = ACTIVE_USER_ID,
|
||||||
|
folder = networkFolder.toEncryptedSdkFolder(),
|
||||||
|
)
|
||||||
|
} returns folderView.asSuccess()
|
||||||
|
|
||||||
|
val result = folderManager.updateFolder(folderId = folderId, folderView = folderView)
|
||||||
|
assertEquals(UpdateFolderResult.Success(folderView = folderView), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `syncFolderDeleteFlow should delete folder from disk and update ciphers`() {
|
||||||
|
val userId = "mockId-1"
|
||||||
|
val folderId = "mockId-1"
|
||||||
|
val cipher = createMockCipher(number = 1, folderId = folderId)
|
||||||
|
val updatedCipher = createMockCipher(number = 1, folderId = null)
|
||||||
|
|
||||||
|
coEvery { vaultDiskSource.deleteFolder(userId = userId, folderId = folderId) } just runs
|
||||||
|
coEvery { vaultDiskSource.getCiphers(userId = userId) } returns listOf(cipher)
|
||||||
|
coEvery { vaultDiskSource.saveCipher(userId = userId, cipher = updatedCipher) } just runs
|
||||||
|
|
||||||
|
mutableSyncFolderDeleteFlow.tryEmit(
|
||||||
|
SyncFolderDeleteData(userId = userId, folderId = folderId),
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
vaultDiskSource.deleteFolder(userId = userId, folderId = folderId)
|
||||||
|
vaultDiskSource.getCiphers(userId = userId)
|
||||||
|
vaultDiskSource.saveCipher(userId = userId, cipher = updatedCipher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `syncFolderUpsertFlow create with local folder should do nothing`() = runTest {
|
||||||
|
val number = 1
|
||||||
|
val userId = ACTIVE_USER_ID
|
||||||
|
val folderId = "mockId-$number"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folder = createMockFolder(number = number)
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.getFolders(userId = userId)
|
||||||
|
} returns MutableStateFlow(listOf(folder))
|
||||||
|
|
||||||
|
mutableSyncFolderUpsertFlow.tryEmit(
|
||||||
|
SyncFolderUpsertData(
|
||||||
|
folderId = folderId,
|
||||||
|
revisionDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||||
|
isUpdate = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 0) {
|
||||||
|
folderService.getFolder(folderId = any())
|
||||||
|
vaultDiskSource.saveFolder(userId = any(), folder = any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `syncFolderUpsertFlow update with no local folder should do nothing`() = runTest {
|
||||||
|
val number = 1
|
||||||
|
val userId = ACTIVE_USER_ID
|
||||||
|
val folderId = "mockId-$number"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.getFolders(userId = userId)
|
||||||
|
} returns MutableStateFlow(emptyList())
|
||||||
|
|
||||||
|
mutableSyncFolderUpsertFlow.tryEmit(
|
||||||
|
SyncFolderUpsertData(
|
||||||
|
folderId = folderId,
|
||||||
|
revisionDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||||
|
isUpdate = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 0) {
|
||||||
|
folderService.getFolder(folderId = any())
|
||||||
|
vaultDiskSource.saveFolder(userId = any(), folder = any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `syncFolderUpsertFlow update with more recent local folder should do nothing`() = runTest {
|
||||||
|
val number = 1
|
||||||
|
val userId = ACTIVE_USER_ID
|
||||||
|
val folderId = "mockId-$number"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val folder = createMockFolder(number = number)
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.getFolders(userId = userId)
|
||||||
|
} returns MutableStateFlow(listOf(folder))
|
||||||
|
|
||||||
|
mutableSyncFolderUpsertFlow.tryEmit(
|
||||||
|
SyncFolderUpsertData(
|
||||||
|
folderId = folderId,
|
||||||
|
revisionDate = ZonedDateTime.ofInstant(
|
||||||
|
Instant.ofEpochSecond(0), ZoneId.of("UTC"),
|
||||||
|
),
|
||||||
|
isUpdate = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 0) {
|
||||||
|
folderService.getFolder(folderId = any())
|
||||||
|
vaultDiskSource.saveFolder(userId = any(), folder = any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `syncFolderUpsertFlow valid create success should make a request for a folder and then store it`() =
|
||||||
|
runTest {
|
||||||
|
val number = 1
|
||||||
|
val userId = ACTIVE_USER_ID
|
||||||
|
val folderId = "mockId-$number"
|
||||||
|
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
coEvery {
|
||||||
|
vaultDiskSource.getFolders(userId = userId)
|
||||||
|
} returns MutableStateFlow(emptyList())
|
||||||
|
val folder = mockk<SyncResponseJson.Folder>()
|
||||||
|
coEvery { folderService.getFolder(folderId = folderId) } returns folder.asSuccess()
|
||||||
|
coEvery { vaultDiskSource.saveFolder(userId = userId, folder = folder) } just runs
|
||||||
|
|
||||||
|
mutableSyncFolderUpsertFlow.tryEmit(
|
||||||
|
SyncFolderUpsertData(
|
||||||
|
folderId = folderId,
|
||||||
|
revisionDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||||
|
isUpdate = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
folderService.getFolder(folderId = folderId)
|
||||||
|
vaultDiskSource.saveFolder(userId = userId, folder = folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `syncFolderUpsertFlow valid update success should make a request for a folder and then store it`() =
|
||||||
|
runTest {
|
||||||
|
val number = 1
|
||||||
|
val userId = ACTIVE_USER_ID
|
||||||
|
val folderId = "mockId-$number"
|
||||||
|
|
||||||
|
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))
|
||||||
|
val folder = mockk<SyncResponseJson.Folder>()
|
||||||
|
coEvery { folderService.getFolder(folderId = folderId) } returns folder.asSuccess()
|
||||||
|
coEvery { vaultDiskSource.saveFolder(userId = userId, folder = folder) } just runs
|
||||||
|
|
||||||
|
mutableSyncFolderUpsertFlow.tryEmit(
|
||||||
|
SyncFolderUpsertData(
|
||||||
|
folderId = folderId,
|
||||||
|
revisionDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||||
|
isUpdate = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
folderService.getFolder(folderId = folderId)
|
||||||
|
vaultDiskSource.saveFolder(userId = userId, folder = folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||||
|
Instant.parse("2023-10-27T12:00:00Z"),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val ACTIVE_USER_ID: String = "mockId-1"
|
||||||
|
|
||||||
|
private val MOCK_PROFILE = AccountJson.Profile(
|
||||||
|
userId = ACTIVE_USER_ID,
|
||||||
|
email = "email",
|
||||||
|
isEmailVerified = true,
|
||||||
|
name = null,
|
||||||
|
stamp = "mockSecurityStamp-1",
|
||||||
|
organizationId = null,
|
||||||
|
avatarColorHex = null,
|
||||||
|
hasPremium = false,
|
||||||
|
forcePasswordResetReason = null,
|
||||||
|
kdfType = null,
|
||||||
|
kdfIterations = null,
|
||||||
|
kdfMemory = null,
|
||||||
|
kdfParallelism = null,
|
||||||
|
userDecryptionOptions = null,
|
||||||
|
isTwoFactorEnabled = false,
|
||||||
|
creationDate = ZonedDateTime.parse("2024-09-13T01:00:00.00Z"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val MOCK_ACCOUNT = AccountJson(
|
||||||
|
profile = MOCK_PROFILE,
|
||||||
|
tokens = AccountTokensJson(
|
||||||
|
accessToken = "accessToken",
|
||||||
|
refreshToken = "refreshToken",
|
||||||
|
),
|
||||||
|
settings = AccountJson.Settings(
|
||||||
|
environmentUrlData = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val MOCK_USER_STATE = UserStateJson(
|
||||||
|
activeUserId = ACTIVE_USER_ID,
|
||||||
|
accounts = mapOf(
|
||||||
|
ACTIVE_USER_ID to MOCK_ACCOUNT,
|
||||||
|
),
|
||||||
|
)
|
||||||
@ -15,22 +15,18 @@ import com.bitwarden.data.manager.DispatcherManager
|
|||||||
import com.bitwarden.exporters.ExportFormat
|
import com.bitwarden.exporters.ExportFormat
|
||||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||||
import com.bitwarden.network.model.CipherTypeJson
|
import com.bitwarden.network.model.CipherTypeJson
|
||||||
import com.bitwarden.network.model.FolderJsonRequest
|
|
||||||
import com.bitwarden.network.model.SyncResponseJson
|
import com.bitwarden.network.model.SyncResponseJson
|
||||||
import com.bitwarden.network.model.UpdateFolderResponseJson
|
|
||||||
import com.bitwarden.network.model.createMockCipher
|
import com.bitwarden.network.model.createMockCipher
|
||||||
import com.bitwarden.network.model.createMockCollection
|
import com.bitwarden.network.model.createMockCollection
|
||||||
import com.bitwarden.network.model.createMockDomains
|
import com.bitwarden.network.model.createMockDomains
|
||||||
import com.bitwarden.network.model.createMockFolder
|
import com.bitwarden.network.model.createMockFolder
|
||||||
import com.bitwarden.network.model.createMockOrganizationKeys
|
import com.bitwarden.network.model.createMockOrganizationKeys
|
||||||
import com.bitwarden.network.model.createMockSend
|
import com.bitwarden.network.model.createMockSend
|
||||||
import com.bitwarden.network.service.FolderService
|
|
||||||
import com.bitwarden.sdk.Fido2CredentialStore
|
import com.bitwarden.sdk.Fido2CredentialStore
|
||||||
import com.bitwarden.send.SendView
|
import com.bitwarden.send.SendView
|
||||||
import com.bitwarden.vault.CipherType
|
import com.bitwarden.vault.CipherType
|
||||||
import com.bitwarden.vault.CipherView
|
import com.bitwarden.vault.CipherView
|
||||||
import com.bitwarden.vault.DecryptCipherListResult
|
import com.bitwarden.vault.DecryptCipherListResult
|
||||||
import com.bitwarden.vault.Folder
|
|
||||||
import com.bitwarden.vault.FolderView
|
import com.bitwarden.vault.FolderView
|
||||||
import com.bitwarden.vault.TotpResponse
|
import com.bitwarden.vault.TotpResponse
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||||
@ -43,8 +39,6 @@ import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
|
|||||||
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
|
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
|
||||||
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderDeleteData
|
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockAccount
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockAccount
|
||||||
@ -65,14 +59,11 @@ import com.x8bit.bitwarden.data.vault.manager.VaultSyncManager
|
|||||||
import com.x8bit.bitwarden.data.vault.manager.model.ImportCxfPayloadResult
|
import com.x8bit.bitwarden.data.vault.manager.model.ImportCxfPayloadResult
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.SyncVaultDataResult
|
import com.x8bit.bitwarden.data.vault.manager.model.SyncVaultDataResult
|
||||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult
|
import com.x8bit.bitwarden.data.vault.repository.model.ImportCredentialsResult
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||||
@ -81,7 +72,6 @@ import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
|
|||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolder
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
|
||||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
|
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.util.createVerificationCodeItem
|
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.util.createVerificationCodeItem
|
||||||
@ -113,7 +103,6 @@ import java.security.GeneralSecurityException
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
@ -131,7 +120,6 @@ class VaultRepositoryTest {
|
|||||||
private val settingsDiskSource = mockk<SettingsDiskSource> {
|
private val settingsDiskSource = mockk<SettingsDiskSource> {
|
||||||
every { getLastSyncTime(userId = any()) } returns clock.instant()
|
every { getLastSyncTime(userId = any()) } returns clock.instant()
|
||||||
}
|
}
|
||||||
private val folderService: FolderService = mockk()
|
|
||||||
private val mutableGetCiphersFlow: MutableStateFlow<List<SyncResponseJson.Cipher>> =
|
private val mutableGetCiphersFlow: MutableStateFlow<List<SyncResponseJson.Cipher>> =
|
||||||
MutableStateFlow(listOf(createMockCipher(1)))
|
MutableStateFlow(listOf(createMockCipher(1)))
|
||||||
private val vaultDiskSource: VaultDiskSource = mockk {
|
private val vaultDiskSource: VaultDiskSource = mockk {
|
||||||
@ -169,18 +157,13 @@ class VaultRepositoryTest {
|
|||||||
every { databaseSchemeChangeFlow } returns mutableDatabaseSchemeChangeFlow
|
every { databaseSchemeChangeFlow } returns mutableDatabaseSchemeChangeFlow
|
||||||
}
|
}
|
||||||
private val mutableFullSyncFlow = bufferedMutableSharedFlow<Unit>()
|
private val mutableFullSyncFlow = bufferedMutableSharedFlow<Unit>()
|
||||||
private val mutableSyncFolderDeleteFlow = bufferedMutableSharedFlow<SyncFolderDeleteData>()
|
|
||||||
private val mutableSyncFolderUpsertFlow = bufferedMutableSharedFlow<SyncFolderUpsertData>()
|
|
||||||
private val pushManager: PushManager = mockk {
|
private val pushManager: PushManager = mockk {
|
||||||
every { fullSyncFlow } returns mutableFullSyncFlow
|
every { fullSyncFlow } returns mutableFullSyncFlow
|
||||||
every { syncFolderDeleteFlow } returns mutableSyncFolderDeleteFlow
|
|
||||||
every { syncFolderUpsertFlow } returns mutableSyncFolderUpsertFlow
|
|
||||||
}
|
}
|
||||||
private val vaultSyncManager: VaultSyncManager = mockk()
|
private val vaultSyncManager: VaultSyncManager = mockk()
|
||||||
private val credentialExchangeImportManager: CredentialExchangeImportManager = mockk()
|
private val credentialExchangeImportManager: CredentialExchangeImportManager = mockk()
|
||||||
|
|
||||||
private val vaultRepository = VaultRepositoryImpl(
|
private val vaultRepository = VaultRepositoryImpl(
|
||||||
folderService = folderService,
|
|
||||||
vaultDiskSource = vaultDiskSource,
|
vaultDiskSource = vaultDiskSource,
|
||||||
vaultSdkSource = vaultSdkSource,
|
vaultSdkSource = vaultSdkSource,
|
||||||
authDiskSource = fakeAuthDiskSource,
|
authDiskSource = fakeAuthDiskSource,
|
||||||
@ -190,6 +173,7 @@ class VaultRepositoryTest {
|
|||||||
totpCodeManager = totpCodeManager,
|
totpCodeManager = totpCodeManager,
|
||||||
pushManager = pushManager,
|
pushManager = pushManager,
|
||||||
cipherManager = mockk(),
|
cipherManager = mockk(),
|
||||||
|
folderManager = mockk(),
|
||||||
sendManager = mockk(),
|
sendManager = mockk(),
|
||||||
clock = clock,
|
clock = clock,
|
||||||
databaseSchemeManager = databaseSchemeManager,
|
databaseSchemeManager = databaseSchemeManager,
|
||||||
@ -2063,362 +2047,6 @@ class VaultRepositoryTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `deleteFolder with no active user should return DeleteFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = null
|
|
||||||
|
|
||||||
val result = vaultRepository.deleteFolder("Test")
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
DeleteFolderResult.Error(error = NoActiveUserException()),
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `DeleteFolder with folderService Delete failure should return DeleteFolderResult Failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val error = Throwable("fail")
|
|
||||||
val folderId = "mockId-1"
|
|
||||||
coEvery { folderService.deleteFolder(folderId) } returns error.asFailure()
|
|
||||||
|
|
||||||
val result = vaultRepository.deleteFolder(folderId)
|
|
||||||
assertEquals(DeleteFolderResult.Error(error = error), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `DeleteFolder with folderService Delete success should return DeleteFolderResult Success and update ciphers`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val userId = MOCK_USER_STATE.activeUserId
|
|
||||||
val folderId = "mockFolderId-1"
|
|
||||||
val mockCipher = createMockCipher(number = 1)
|
|
||||||
val ciphers = listOf(mockCipher, createMockCipher(number = 2))
|
|
||||||
coEvery { folderService.deleteFolder(folderId) } returns Unit.asSuccess()
|
|
||||||
coEvery { vaultDiskSource.deleteFolder(userId = userId, folderId = folderId) } just runs
|
|
||||||
coEvery { vaultDiskSource.getCiphers(userId = userId) } returns ciphers
|
|
||||||
coEvery {
|
|
||||||
vaultDiskSource.saveCipher(
|
|
||||||
userId = userId,
|
|
||||||
cipher = mockCipher.copy(folderId = null),
|
|
||||||
)
|
|
||||||
} just runs
|
|
||||||
|
|
||||||
val result = vaultRepository.deleteFolder(folderId = folderId)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) {
|
|
||||||
vaultDiskSource.saveCipher(
|
|
||||||
userId = userId,
|
|
||||||
cipher = mockCipher.copy(folderId = null),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(DeleteFolderResult.Success, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `createFolder with no active user should return CreateFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = null
|
|
||||||
|
|
||||||
val result = vaultRepository.createFolder(mockk())
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
CreateFolderResult.Error(NoActiveUserException()),
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `createFolder with folderService Delete failure should return DeleteFolderResult Failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderId = "mockId-1"
|
|
||||||
val error = Throwable("fail")
|
|
||||||
coEvery { folderService.deleteFolder(folderId) } returns error.asFailure()
|
|
||||||
|
|
||||||
val result = vaultRepository.deleteFolder(folderId)
|
|
||||||
assertEquals(DeleteFolderResult.Error(error = error), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `createFolder with encryptFolder failure should return CreateFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = null,
|
|
||||||
name = "TestName",
|
|
||||||
revisionDate = DateTime.now(),
|
|
||||||
)
|
|
||||||
val error = IllegalStateException()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns error.asFailure()
|
|
||||||
|
|
||||||
val result = vaultRepository.createFolder(folderView)
|
|
||||||
assertEquals(CreateFolderResult.Error(error = error), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `createFolder with folderService failure should return CreateFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
val date = DateTime.now()
|
|
||||||
val testFolderName = "TestName"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = null,
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = date,
|
|
||||||
)
|
|
||||||
val error = IllegalStateException()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns Folder(id = null, name = testFolderName, revisionDate = date).asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
folderService.createFolder(
|
|
||||||
body = FolderJsonRequest(testFolderName),
|
|
||||||
)
|
|
||||||
} returns error.asFailure()
|
|
||||||
|
|
||||||
val result = vaultRepository.createFolder(folderView)
|
|
||||||
assertEquals(CreateFolderResult.Error(error = error), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `createFolder with folderService createFolder should return CreateFolderResult success`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val date = DateTime.now()
|
|
||||||
val testFolderName = "TestName"
|
|
||||||
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = null,
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = date,
|
|
||||||
)
|
|
||||||
|
|
||||||
val networkFolder = SyncResponseJson.Folder(
|
|
||||||
id = "1",
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = ZonedDateTime.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns Folder(id = null, name = testFolderName, revisionDate = date).asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
folderService.createFolder(
|
|
||||||
body = FolderJsonRequest(testFolderName),
|
|
||||||
)
|
|
||||||
} returns networkFolder.asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultDiskSource.saveFolder(
|
|
||||||
MOCK_USER_STATE.activeUserId,
|
|
||||||
networkFolder,
|
|
||||||
)
|
|
||||||
} just runs
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.decryptFolder(
|
|
||||||
MOCK_USER_STATE.activeUserId,
|
|
||||||
networkFolder.toEncryptedSdkFolder(),
|
|
||||||
)
|
|
||||||
} returns folderView.asSuccess()
|
|
||||||
|
|
||||||
val result = vaultRepository.createFolder(folderView)
|
|
||||||
assertEquals(CreateFolderResult.Success(folderView), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `updateFolder with no active user should return UpdateFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = null
|
|
||||||
|
|
||||||
val result = vaultRepository.updateFolder("Test", mockk())
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
UpdateFolderResult.Error(errorMessage = null, error = NoActiveUserException()),
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `updateFolder with encryptFolder failure should return UpdateFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderId = "testId"
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = folderId,
|
|
||||||
name = "TestName",
|
|
||||||
revisionDate = DateTime.now(),
|
|
||||||
)
|
|
||||||
val error = IllegalStateException()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns error.asFailure()
|
|
||||||
|
|
||||||
val result = vaultRepository.updateFolder(folderId, folderView)
|
|
||||||
|
|
||||||
assertEquals(UpdateFolderResult.Error(errorMessage = null, error = error), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `updateFolder with folderService failure should return UpdateFolderResult failure`() =
|
|
||||||
runTest {
|
|
||||||
val date = DateTime.now()
|
|
||||||
val testFolderName = "TestName"
|
|
||||||
val folderId = "testId"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = folderId,
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = date,
|
|
||||||
)
|
|
||||||
val error = IllegalStateException()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns Folder(id = folderId, name = testFolderName, revisionDate = date).asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
folderService.updateFolder(
|
|
||||||
folderId = folderId,
|
|
||||||
body = FolderJsonRequest(testFolderName),
|
|
||||||
)
|
|
||||||
} returns error.asFailure()
|
|
||||||
|
|
||||||
val result = vaultRepository.updateFolder(folderId, folderView)
|
|
||||||
assertEquals(UpdateFolderResult.Error(errorMessage = null, error = error), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `updateFolder with folderService updateFolder Invalid response should return UpdateFolderResult Error with a non-null message`() =
|
|
||||||
runTest {
|
|
||||||
val date = DateTime.now()
|
|
||||||
val testFolderName = "TestName"
|
|
||||||
val folderId = "testId"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = folderId,
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = date,
|
|
||||||
)
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns Folder(id = folderId, name = testFolderName, revisionDate = date).asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
folderService.updateFolder(
|
|
||||||
folderId = folderId,
|
|
||||||
body = FolderJsonRequest(testFolderName),
|
|
||||||
)
|
|
||||||
} returns UpdateFolderResponseJson
|
|
||||||
.Invalid(
|
|
||||||
message = "You do not have permission to edit this.",
|
|
||||||
validationErrors = null,
|
|
||||||
)
|
|
||||||
.asSuccess()
|
|
||||||
|
|
||||||
val result = vaultRepository.updateFolder(folderId, folderView)
|
|
||||||
assertEquals(
|
|
||||||
UpdateFolderResult.Error(
|
|
||||||
errorMessage = "You do not have permission to edit this.",
|
|
||||||
error = null,
|
|
||||||
),
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `updateFolder with folderService updateFolder success should return UpdateFolderResult success`() =
|
|
||||||
runTest {
|
|
||||||
val date = DateTime.now()
|
|
||||||
val testFolderName = "TestName"
|
|
||||||
val folderId = "testId"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
|
|
||||||
val folderView = FolderView(
|
|
||||||
id = folderId,
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = date,
|
|
||||||
)
|
|
||||||
|
|
||||||
val networkFolder = SyncResponseJson.Folder(
|
|
||||||
id = "1",
|
|
||||||
name = testFolderName,
|
|
||||||
revisionDate = ZonedDateTime.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.encryptFolder(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folder = folderView,
|
|
||||||
)
|
|
||||||
} returns Folder(id = folderId, name = testFolderName, revisionDate = date).asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
folderService.updateFolder(
|
|
||||||
folderId = folderId,
|
|
||||||
body = FolderJsonRequest(testFolderName),
|
|
||||||
)
|
|
||||||
} returns UpdateFolderResponseJson
|
|
||||||
.Success(folder = networkFolder)
|
|
||||||
.asSuccess()
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultDiskSource.saveFolder(
|
|
||||||
MOCK_USER_STATE.activeUserId,
|
|
||||||
networkFolder,
|
|
||||||
)
|
|
||||||
} just runs
|
|
||||||
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.decryptFolder(
|
|
||||||
MOCK_USER_STATE.activeUserId,
|
|
||||||
networkFolder.toEncryptedSdkFolder(),
|
|
||||||
)
|
|
||||||
} returns folderView.asSuccess()
|
|
||||||
|
|
||||||
val result = vaultRepository.updateFolder(folderId, folderView)
|
|
||||||
assertEquals(UpdateFolderResult.Success(folderView), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getAuthCodeFlow with no active user should emit an error`() = runTest {
|
fun `getAuthCodeFlow with no active user should emit an error`() = runTest {
|
||||||
fakeAuthDiskSource.userState = null
|
fakeAuthDiskSource.userState = null
|
||||||
@ -2533,210 +2161,6 @@ class VaultRepositoryTest {
|
|||||||
coVerify { vaultSyncManager.sync(any(), any()) }
|
coVerify { vaultSyncManager.sync(any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `syncFolderDeleteFlow should delete folder from disk and update ciphers`() {
|
|
||||||
val userId = "mockId-1"
|
|
||||||
val folderId = "mockId-1"
|
|
||||||
val cipher = createMockCipher(number = 1, folderId = folderId)
|
|
||||||
val updatedCipher = createMockCipher(number = 1, folderId = null)
|
|
||||||
|
|
||||||
coEvery { vaultDiskSource.deleteFolder(userId = userId, folderId = folderId) } just runs
|
|
||||||
coEvery { vaultDiskSource.getCiphers(userId = userId) } returns listOf(cipher)
|
|
||||||
coEvery { vaultDiskSource.saveCipher(userId = userId, cipher = updatedCipher) } just runs
|
|
||||||
|
|
||||||
mutableSyncFolderDeleteFlow.tryEmit(
|
|
||||||
SyncFolderDeleteData(userId = userId, folderId = folderId),
|
|
||||||
)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) {
|
|
||||||
vaultDiskSource.deleteFolder(userId = userId, folderId = folderId)
|
|
||||||
vaultDiskSource.getCiphers(userId = userId)
|
|
||||||
vaultDiskSource.saveCipher(userId = userId, cipher = updatedCipher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `syncFolderUpsertFlow create with local folder should do nothing`() = runTest {
|
|
||||||
val number = 1
|
|
||||||
val folderId = "mockId-$number"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
|
|
||||||
val folderView = createMockFolderView(number = number)
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.decryptFolderList(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folderList = listOf(createMockSdkFolder(number = number)),
|
|
||||||
)
|
|
||||||
} returns listOf(folderView).asSuccess()
|
|
||||||
|
|
||||||
val foldersFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
|
|
||||||
setupVaultDiskSourceFlows(foldersFlow = foldersFlow)
|
|
||||||
|
|
||||||
vaultRepository.foldersStateFlow.test {
|
|
||||||
// Populate and consume items related to the folders flow
|
|
||||||
awaitItem()
|
|
||||||
foldersFlow.tryEmit(listOf(createMockFolder(number = number)))
|
|
||||||
awaitItem()
|
|
||||||
|
|
||||||
mutableSyncFolderUpsertFlow.tryEmit(
|
|
||||||
SyncFolderUpsertData(
|
|
||||||
folderId = folderId,
|
|
||||||
revisionDate = ZonedDateTime.now(),
|
|
||||||
isUpdate = false,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
coVerify(exactly = 0) {
|
|
||||||
folderService.getFolder(any())
|
|
||||||
vaultDiskSource.saveFolder(any(), any())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `syncFolderUpsertFlow update with no local folder should do nothing`() = runTest {
|
|
||||||
val number = 1
|
|
||||||
val folderId = "mockId-$number"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.decryptFolderList(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folderList = listOf(),
|
|
||||||
)
|
|
||||||
} returns listOf<FolderView>().asSuccess()
|
|
||||||
|
|
||||||
val foldersFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
|
|
||||||
setupVaultDiskSourceFlows(foldersFlow = foldersFlow)
|
|
||||||
|
|
||||||
vaultRepository.foldersStateFlow.test {
|
|
||||||
// Populate and consume items related to the folders flow
|
|
||||||
awaitItem()
|
|
||||||
foldersFlow.tryEmit(listOf())
|
|
||||||
awaitItem()
|
|
||||||
|
|
||||||
mutableSyncFolderUpsertFlow.tryEmit(
|
|
||||||
SyncFolderUpsertData(
|
|
||||||
folderId = folderId,
|
|
||||||
revisionDate = ZonedDateTime.now(),
|
|
||||||
isUpdate = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
coVerify(exactly = 0) {
|
|
||||||
folderService.getFolder(any())
|
|
||||||
vaultDiskSource.saveFolder(any(), any())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `syncFolderUpsertFlow update with more recent local folder should do nothing`() = runTest {
|
|
||||||
val number = 1
|
|
||||||
val folderId = "mockId-$number"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
|
|
||||||
val folderView = createMockFolderView(number = number)
|
|
||||||
coEvery {
|
|
||||||
vaultSdkSource.decryptFolderList(
|
|
||||||
userId = MOCK_USER_STATE.activeUserId,
|
|
||||||
folderList = listOf(createMockSdkFolder(number = number)),
|
|
||||||
)
|
|
||||||
} returns listOf(folderView).asSuccess()
|
|
||||||
|
|
||||||
val foldersFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
|
|
||||||
setupVaultDiskSourceFlows(foldersFlow = foldersFlow)
|
|
||||||
|
|
||||||
vaultRepository.foldersStateFlow.test {
|
|
||||||
// Populate and consume items related to the folders flow
|
|
||||||
awaitItem()
|
|
||||||
foldersFlow.tryEmit(listOf(createMockFolder(number = number)))
|
|
||||||
awaitItem()
|
|
||||||
|
|
||||||
mutableSyncFolderUpsertFlow.tryEmit(
|
|
||||||
SyncFolderUpsertData(
|
|
||||||
folderId = folderId,
|
|
||||||
revisionDate = ZonedDateTime.ofInstant(
|
|
||||||
Instant.ofEpochSecond(0), ZoneId.of("UTC"),
|
|
||||||
),
|
|
||||||
isUpdate = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
coVerify(exactly = 0) {
|
|
||||||
folderService.getFolder(any())
|
|
||||||
vaultDiskSource.saveFolder(any(), any())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `syncFolderUpsertFlow valid create success should make a request for a folder and then store it`() =
|
|
||||||
runTest {
|
|
||||||
val number = 1
|
|
||||||
val userId = MOCK_USER_STATE.activeUserId
|
|
||||||
val folderId = "mockId-$number"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
coEvery {
|
|
||||||
vaultDiskSource.getFolders(userId = userId)
|
|
||||||
} returns MutableStateFlow(emptyList())
|
|
||||||
val folder = mockk<SyncResponseJson.Folder>()
|
|
||||||
coEvery { folderService.getFolder(folderId = folderId) } returns folder.asSuccess()
|
|
||||||
coEvery { vaultDiskSource.saveFolder(userId = userId, folder = folder) } just runs
|
|
||||||
|
|
||||||
mutableSyncFolderUpsertFlow.tryEmit(
|
|
||||||
SyncFolderUpsertData(
|
|
||||||
folderId = folderId,
|
|
||||||
revisionDate = ZonedDateTime.now(clock),
|
|
||||||
isUpdate = false,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) {
|
|
||||||
folderService.getFolder(folderId = folderId)
|
|
||||||
vaultDiskSource.saveFolder(userId = userId, folder = folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
@Test
|
|
||||||
fun `syncFolderUpsertFlow valid update success should make a request for a folder and then store it`() =
|
|
||||||
runTest {
|
|
||||||
val number = 1
|
|
||||||
val userId = MOCK_USER_STATE.activeUserId
|
|
||||||
val folderId = "mockId-$number"
|
|
||||||
|
|
||||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
|
||||||
val folderView = createMockFolder(
|
|
||||||
number = number,
|
|
||||||
revisionDate = ZonedDateTime.now(clock).minus(5, ChronoUnit.MINUTES),
|
|
||||||
)
|
|
||||||
coEvery {
|
|
||||||
vaultDiskSource.getFolders(userId = userId)
|
|
||||||
} returns MutableStateFlow(listOf(folderView))
|
|
||||||
val folder = mockk<SyncResponseJson.Folder>()
|
|
||||||
coEvery { folderService.getFolder(folderId = folderId) } returns folder.asSuccess()
|
|
||||||
coEvery { vaultDiskSource.saveFolder(userId = userId, folder = folder) } just runs
|
|
||||||
|
|
||||||
mutableSyncFolderUpsertFlow.tryEmit(
|
|
||||||
SyncFolderUpsertData(
|
|
||||||
folderId = folderId,
|
|
||||||
revisionDate = ZonedDateTime.now(clock),
|
|
||||||
isUpdate = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) {
|
|
||||||
folderService.getFolder(folderId)
|
|
||||||
vaultDiskSource.saveFolder(userId = userId, folder = folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `exportVaultDataToString should return a success result when data is successfully converted for export`() =
|
fun `exportVaultDataToString should return a success result when data is successfully converted for export`() =
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user