Adding the Repository folder calls. (#813)

This commit is contained in:
Oleg Semenenko 2024-01-28 10:50:14 -06:00 committed by Álison Fernandes
parent fa551fa6ab
commit 3be37766e2
15 changed files with 654 additions and 2 deletions

View file

@ -39,6 +39,11 @@ interface VaultDiskSource {
*/
fun getDomains(userId: String): Flow<SyncResponseJson.Domains>
/**
* Deletes a folder from the data source for the given [userId] and [folderId].
*/
suspend fun deleteFolder(userId: String, folderId: String)
/**
* Saves a folder to the data source for the given [userId].
*/

View file

@ -114,6 +114,10 @@ class VaultDiskSourceImpl(
json.decodeFromString<SyncResponseJson.Domains>(entity.domainsJson)
}
override suspend fun deleteFolder(userId: String, folderId: String) {
foldersDao.deleteFolder(userId = userId, folderId = folderId)
}
override suspend fun saveFolder(userId: String, folder: SyncResponseJson.Folder) {
foldersDao.insertFolder(
folder = FolderEntity(

View file

@ -229,6 +229,18 @@ interface VaultSdkSource {
sendList: List<Send>,
): Result<List<SendView>>
/**
* Encrypts a [FolderView] for the user with the given [userId], returning a [Folder] wrapped
* in a [Result].
*
* This should only be called after a successful call to [initializeCrypto] for the associated
* user.
*/
suspend fun encryptFolder(
userId: String,
folder: FolderView,
): Result<Folder>
/**
* Decrypts a [Folder] for the user with the given [userId], returning a [FolderView] wrapped
* in a [Result].

View file

@ -240,6 +240,17 @@ class VaultSdkSourceImpl(
}
}
override suspend fun encryptFolder(
userId: String,
folder: FolderView,
): Result<Folder> =
runCatching {
getClient(userId = userId)
.vault()
.folders()
.encrypt(folder)
}
override suspend fun decryptFolder(
userId: String,
folder: Folder,

View file

@ -13,9 +13,11 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
@ -25,6 +27,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
@ -283,4 +286,19 @@ interface VaultRepository : VaultLockManager {
attachmentId: String,
cipherView: CipherView,
): DeleteAttachmentResult
/**
* 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
}

View file

@ -31,8 +31,10 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonReq
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateFolderResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.FolderService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@ -42,9 +44,11 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
@ -54,6 +58,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
@ -61,10 +66,12 @@ import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabetically
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipher
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherResponse
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkFolder
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend
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.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.toEncryptedSdkSend
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
@ -80,6 +87,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
@ -107,6 +115,7 @@ class VaultRepositoryImpl(
private val syncService: SyncService,
private val ciphersService: CiphersService,
private val sendsService: SendsService,
private val folderService: FolderService,
private val vaultDiskSource: VaultDiskSource,
private val vaultSdkSource: VaultSdkSource,
private val authDiskSource: AuthDiskSource,
@ -917,6 +926,95 @@ class VaultRepositoryImpl(
)
}
override suspend fun createFolder(folderView: FolderView): CreateFolderResult {
val userId = activeUserId ?: return CreateFolderResult.Error
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 },
)
}
override suspend fun updateFolder(
folderId: String,
folderView: FolderView,
): UpdateFolderResult {
val userId = activeUserId ?: return UpdateFolderResult.Error(null)
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) },
)
}
is UpdateFolderResponseJson.Invalid -> {
UpdateFolderResult.Error(response.message)
}
}
},
onFailure = { UpdateFolderResult.Error(it.message) },
)
}
override suspend fun deleteFolder(folderId: String): DeleteFolderResult {
val userId = activeUserId ?: return DeleteFolderResult.Error
return folderService
.deleteFolder(
folderId = folderId,
)
.onSuccess {
clearFolderIdFromCiphers(folderId, userId)
vaultDiskSource.deleteFolder(userId, folderId)
}
.fold(
onSuccess = { DeleteFolderResult.Success },
onFailure = { DeleteFolderResult.Error },
)
}
private suspend fun clearFolderIdFromCiphers(folderId: String, userId: String) {
vaultDiskSource.getCiphers(userId).firstOrNull()?.forEach {
if (it.folderId == folderId) {
vaultDiskSource.saveCipher(
userId, it.copy(folderId = null),
)
}
}
}
/**
* Checks if the given [userId] has an associated encrypted PIN key but not a pin-protected user
* key. This indicates a scenario in which a user has requested PIN unlocking but requires

View file

@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.FolderService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@ -33,6 +34,7 @@ object VaultRepositoryModule {
syncService: SyncService,
sendsService: SendsService,
ciphersService: CiphersService,
folderService: FolderService,
vaultDiskSource: VaultDiskSource,
vaultSdkSource: VaultSdkSource,
authDiskSource: AuthDiskSource,
@ -46,6 +48,7 @@ object VaultRepositoryModule {
syncService = syncService,
sendsService = sendsService,
ciphersService = ciphersService,
folderService = folderService,
vaultDiskSource = vaultDiskSource,
vaultSdkSource = vaultSdkSource,
authDiskSource = authDiskSource,

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.vault.repository.model
import com.bitwarden.core.FolderView
/**
* Models result of creating a folder.
*/
sealed class CreateFolderResult {
/**
* Folder created successfully.
*/
data class Success(val folderView: FolderView) : CreateFolderResult()
/**
* Generic error while creating a folder.
*/
data object Error : CreateFolderResult()
}

View file

@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.vault.repository.model
/**
* Models result of deleting a folder.
*/
sealed class DeleteFolderResult {
/**
* Folder deleted successfully.
*/
data object Success : DeleteFolderResult()
/**
* Generic error while deleting a folder.
*/
data object Error : DeleteFolderResult()
}

View file

@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.vault.repository.model
import com.bitwarden.core.FolderView
/**
* Models result of updating a folder.
*/
sealed class UpdateFolderResult {
/**
* Folder updated successfully.
*/
data class Success(val folderView: FolderView) : UpdateFolderResult()
/**
* Generic error while updating a folder. The optional [errorMessage]
* may be displayed directly in the UI when present.
*/
data class Error(val errorMessage: String?) : UpdateFolderResult()
}

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository.util
import com.bitwarden.core.Folder
import com.bitwarden.core.FolderView
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import java.util.Locale
@ -29,3 +30,10 @@ fun SyncResponseJson.Folder.toEncryptedSdkFolder(): Folder =
@JvmName("toAlphabeticallySortedFolderList")
fun List<FolderView>.sortAlphabetically(): List<FolderView> =
this.sortedBy { it.name.uppercase(Locale.getDefault()) }
/**
* Converts a Bitwarden SDK [Folder] objects to a corresponding
* [SyncResponseJson.Folder] object.
*/
fun Folder.toEncryptedNetworkFolder(): FolderJsonRequest =
FolderJsonRequest(name = name)

View file

@ -138,6 +138,18 @@ class VaultDiskSourceTest {
}
}
@Test
fun `DeleteFolder should call deleteFolder`() = runTest {
assertFalse(foldersDao.deleteFolderCalled)
vaultDiskSource.saveFolder(USER_ID, FOLDER_1)
assertEquals(1, foldersDao.storedFolders.size)
vaultDiskSource.deleteFolder(USER_ID, FOLDER_1.id)
assertTrue(foldersDao.deleteFolderCalled)
assertEquals(emptyList<FolderEntity>(), foldersDao.storedFolders)
}
@Test
fun `saveFolder should call insertFolder`() = runTest {
assertFalse(foldersDao.insertFolderCalled)

View file

@ -581,6 +581,34 @@ class VaultSdkSourceTest {
verify { sdkClientManager.getOrCreateClient(userId = userId) }
}
@Test
fun `encryptFolder should call SDK and return a Result with correct data`() = runBlocking {
val userId = "userId"
val expectedResult = mockk<Folder>()
val mockFolder = mockk<FolderView>()
coEvery {
clientVault.folders().encrypt(
folder = mockFolder,
)
} returns expectedResult
val result = vaultSdkSource.encryptFolder(
userId = userId,
folder = mockFolder,
)
assertEquals(
expectedResult.asSuccess(),
result,
)
coVerify {
clientVault.folders().encrypt(
folder = mockFolder,
)
}
verify { sdkClientManager.getOrCreateClient(userId = userId) }
}
@Test
fun `Folder decrypt should call SDK and return a Result with correct data`() = runBlocking {
val userId = "userId"

View file

@ -6,6 +6,7 @@ import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherView
import com.bitwarden.core.CollectionView
import com.bitwarden.core.DateTime
import com.bitwarden.core.Folder
import com.bitwarden.core.FolderView
import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod
@ -29,11 +30,13 @@ import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateFolderResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachmentEncryptResult
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachmentJsonResponse
@ -48,6 +51,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSendJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSyncResponse
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.FolderService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@ -67,9 +71,11 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
@ -78,6 +84,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
@ -88,6 +95,7 @@ import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherRe
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.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.toEncryptedSdkSendList
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.util.createVerificationCodeItem
@ -113,6 +121,7 @@ import java.net.UnknownHostException
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
@Suppress("LargeClass")
@ -128,6 +137,7 @@ class VaultRepositoryTest {
private val syncService: SyncService = mockk()
private val sendsService: SendsService = mockk()
private val ciphersService: CiphersService = mockk()
private val folderService: FolderService = mockk()
private val vaultDiskSource: VaultDiskSource = mockk()
private val totpCodeManager: TotpCodeManager = mockk()
private val vaultSdkSource: VaultSdkSource = mockk {
@ -151,6 +161,7 @@ class VaultRepositoryTest {
syncService = syncService,
sendsService = sendsService,
ciphersService = ciphersService,
folderService = folderService,
vaultDiskSource = vaultDiskSource,
vaultSdkSource = vaultSdkSource,
authDiskSource = fakeAuthDiskSource,
@ -3137,6 +3148,375 @@ 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,
result,
)
}
@Suppress("MaxLineLength")
@Test
fun `DeleteFolder with folderService Delete failure should return DeleteFolderResult Failure`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val folderId = "mockId-1"
coEvery { folderService.deleteFolder(folderId) } returns Throwable("fail").asFailure()
val result = vaultRepository.deleteFolder(folderId)
assertEquals(DeleteFolderResult.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 folderId = "mockFolderId-1"
coEvery { folderService.deleteFolder(folderId) } returns Unit.asSuccess()
coEvery {
vaultDiskSource.deleteFolder(
MOCK_USER_STATE.activeUserId,
folderId,
)
} just runs
val mockCipher = createMockCipher(1)
val mutableCiphersStateFlow =
MutableStateFlow(
listOf(
mockCipher,
createMockCipher(2),
),
)
coEvery {
vaultDiskSource.getCiphers(MOCK_USER_STATE.activeUserId)
} returns mutableCiphersStateFlow
coEvery {
vaultDiskSource.saveCipher(
MOCK_USER_STATE.activeUserId,
mockCipher.copy(
folderId = null,
),
)
} just runs
val result = vaultRepository.deleteFolder(folderId)
coVerify(exactly = 1) {
vaultDiskSource.saveCipher(
MOCK_USER_STATE.activeUserId,
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,
result,
)
}
@Suppress("MaxLineLength")
@Test
fun `createFolder with folderService Delete failure should return DeleteFolderResult Failure`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val folderId = "mockId-1"
coEvery { folderService.deleteFolder(folderId) } returns Throwable("fail").asFailure()
val result = vaultRepository.deleteFolder(folderId)
assertEquals(DeleteFolderResult.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(),
)
coEvery {
vaultSdkSource.encryptFolder(
userId = MOCK_USER_STATE.activeUserId,
folder = folderView,
)
} returns IllegalStateException().asFailure()
val result = vaultRepository.createFolder(folderView)
assertEquals(CreateFolderResult.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,
)
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 IllegalStateException().asFailure()
val result = vaultRepository.createFolder(folderView)
assertEquals(CreateFolderResult.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(null),
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(),
)
coEvery {
vaultSdkSource.encryptFolder(
userId = MOCK_USER_STATE.activeUserId,
folder = folderView,
)
} returns IllegalStateException().asFailure()
val result = vaultRepository.updateFolder(folderId, folderView)
assertEquals(UpdateFolderResult.Error(errorMessage = null), 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,
)
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 IllegalStateException().asFailure()
val result = vaultRepository.updateFolder(folderId, folderView)
assertEquals(UpdateFolderResult.Error(errorMessage = null), 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.",
),
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)
}
@Suppress("MaxLineLength")
@Test
fun `getAuthCodeFlow with no active user should emit an error`() = runTest {
@ -3169,7 +3549,10 @@ class VaultRepositoryTest {
} just runs
every {
settingsDiskSource.storeLastSyncTime(MOCK_USER_STATE.activeUserId, clock.instant())
settingsDiskSource.storeLastSyncTime(
MOCK_USER_STATE.activeUserId,
clock.instant(),
)
} just runs
val stateFlow = MutableStateFlow<DataState<VerificationCodeItem?>>(
@ -3233,7 +3616,10 @@ class VaultRepositoryTest {
)
} just runs
every {
settingsDiskSource.storeLastSyncTime(MOCK_USER_STATE.activeUserId, clock.instant())
settingsDiskSource.storeLastSyncTime(
MOCK_USER_STATE.activeUserId,
clock.instant(),
)
} just runs
val stateFlow = MutableStateFlow<DataState<List<VerificationCodeItem>>>(

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.repository.util
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFolder
@ -58,4 +59,14 @@ class VaultSdkFolderExtensionsTest {
list.sortAlphabetically(),
)
}
@Test
fun `toEncryptedNetworkFolder should convert a SdkFolder to a NetworkFolder`() {
val sdkFolder = createMockSdkFolder(number = 1)
val syncFolder = sdkFolder.toEncryptedNetworkFolder()
assertEquals(
FolderJsonRequest(sdkFolder.name),
syncFolder,
)
}
}