From ba5509511f783f2150f5b7d006ef6aebb3cd8b33 Mon Sep 17 00:00:00 2001 From: Ramsey Smith <142836716+ramsey-livefront@users.noreply.github.com> Date: Fri, 17 Nov 2023 12:52:56 -0700 Subject: [PATCH] BIT-1117: Decrypt sends (#254) --- .../vault/datasource/sdk/VaultSdkSource.kt | 12 + .../datasource/sdk/VaultSdkSourceImpl.kt | 8 + .../data/vault/repository/VaultRepository.kt | 10 +- .../vault/repository/VaultRepositoryImpl.kt | 73 ++++-- .../data/vault/repository/model/SendData.kt | 12 + .../repository/util/VaultSdkSendExtensions.kt | 72 ++++++ .../network/model/SyncResponseSendUtil.kt | 2 +- .../datasource/sdk/VaultSdkSourceTest.kt | 49 ++++ .../datasource/sdk/model/SendViewUtil.kt | 51 ++++ .../datasource/sdk/model/VaultSdkSendUtil.kt | 51 ++++ .../vault/repository/VaultRepositoryTest.kt | 231 +++++++++++++++++- .../util/VaultSdkSendExtensionsTest.kt | 36 +++ 12 files changed, 582 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/SendData.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/SendViewUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkSendUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt index ab94758b09..eeb93e5e40 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt @@ -6,6 +6,8 @@ import com.bitwarden.core.CipherView import com.bitwarden.core.Folder import com.bitwarden.core.FolderView import com.bitwarden.core.InitCryptoRequest +import com.bitwarden.core.Send +import com.bitwarden.core.SendView import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult /** @@ -34,6 +36,16 @@ interface VaultSdkSource { */ suspend fun decryptCipherList(cipherList: List): Result> + /** + * Decrypts a [Send] returning a [SendView] wrapped in a [Result]. + */ + suspend fun decryptSend(send: Send): Result + + /** + * Decrypts a list of [Send]s returning a list of [SendView] wrapped in a [Result]. + */ + suspend fun decryptSendList(sendList: List): Result> + /** * Decrypts a [Folder] returning a [FolderView] wrapped in a [Result]. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt index af23b61c1b..9af7b16f20 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt @@ -6,6 +6,8 @@ import com.bitwarden.core.CipherView import com.bitwarden.core.Folder import com.bitwarden.core.FolderView import com.bitwarden.core.InitCryptoRequest +import com.bitwarden.core.Send +import com.bitwarden.core.SendView import com.bitwarden.sdk.BitwardenException import com.bitwarden.sdk.ClientCrypto import com.bitwarden.sdk.ClientVault @@ -43,6 +45,12 @@ class VaultSdkSourceImpl( override suspend fun decryptCipherList(cipherList: List): Result> = runCatching { cipherList.map { clientVault.ciphers().decrypt(it) } } + override suspend fun decryptSend(send: Send): Result = + runCatching { clientVault.sends().decrypt(send) } + + override suspend fun decryptSendList(sendList: List): Result> = + runCatching { sendList.map { clientVault.sends().decrypt(it) } } + override suspend fun decryptFolder(folder: Folder): Result = runCatching { clientVault.folders().decrypt(folder) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt index d3f27e4f74..70ec45b8b2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository import com.x8bit.bitwarden.data.platform.repository.model.DataState +import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult import kotlinx.coroutines.flow.StateFlow @@ -16,9 +17,14 @@ interface VaultRepository { val vaultDataStateFlow: StateFlow> /** - * Clear the in memory vault data. + * Flow that represents the current send data. */ - fun clearVaultData() + val sendDataStateFlow: StateFlow> + + /** + * Clear any previously unlocked, in-memory data (vault, send, etc). + */ + fun clearUnlockedData() /** * Attempt to sync the vault data. diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt index d58253cf61..36bff96fe8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -10,10 +10,12 @@ import com.x8bit.bitwarden.data.platform.util.flatMap import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource +import com.x8bit.bitwarden.data.vault.repository.model.SendData 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.util.toEncryptedSdkCipherList 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.toVaultUnlockResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -46,11 +48,18 @@ class VaultRepositoryImpl constructor( private val vaultDataMutableStateFlow = MutableStateFlow>(DataState.Loading) + private val sendDataMutableStateFlow = + MutableStateFlow>(DataState.Loading) + override val vaultDataStateFlow: StateFlow> get() = vaultDataMutableStateFlow.asStateFlow() - override fun clearVaultData() { + override val sendDataStateFlow: StateFlow> + get() = sendDataMutableStateFlow.asStateFlow() + + override fun clearUnlockedData() { vaultDataMutableStateFlow.update { DataState.Loading } + sendDataMutableStateFlow.update { DataState.Loading } } override fun sync() { @@ -60,6 +69,11 @@ class VaultRepositoryImpl constructor( DataState.Pending(data = data) } } + sendDataMutableStateFlow.value.data?.let { data -> + sendDataMutableStateFlow.update { + DataState.Pending(data = data) + } + } syncJob = scope.launch { syncService .sync() @@ -69,22 +83,19 @@ class VaultRepositoryImpl constructor( userKey = syncResponse.profile?.key, privateKey = syncResponse.profile?.privateKey, ) - decryptSyncResponseAndUpdateVaultDataState( - syncResponse = syncResponse, - ) + decryptSyncResponseAndUpdateVaultDataState(syncResponse = syncResponse) + decryptSendsAndUpdateSendDataState(sendList = syncResponse.sends) }, onFailure = { throwable -> - vaultDataMutableStateFlow.update { - if (throwable.isNoConnectionError()) { - DataState.NoNetwork( - data = it.data, - ) - } else { - DataState.Error( - error = throwable, - data = it.data, - ) - } + vaultDataMutableStateFlow.update { currentState -> + throwable.toNetworkOrErrorState( + data = currentState.data, + ) + } + sendDataMutableStateFlow.update { currentState -> + throwable.toNetworkOrErrorState( + data = currentState.data, + ) } }, ) @@ -148,16 +159,34 @@ class VaultRepositoryImpl constructor( ) } + private suspend fun decryptSendsAndUpdateSendDataState(sendList: List?) { + val newState = vaultSdkSource + .decryptSendList( + sendList = sendList + .orEmpty() + .toEncryptedSdkSendList(), + ) + .fold( + onSuccess = { DataState.Loaded(data = SendData(sendViewList = it)) }, + onFailure = { DataState.Error(error = it) }, + ) + sendDataMutableStateFlow.update { newState } + } + private suspend fun decryptSyncResponseAndUpdateVaultDataState(syncResponse: SyncResponseJson) { val newState = vaultSdkSource .decryptCipherList( - cipherList = (syncResponse.ciphers ?: emptyList()) + cipherList = syncResponse + .ciphers + .orEmpty() .toEncryptedSdkCipherList(), ) .flatMap { decryptedCipherList -> vaultSdkSource .decryptFolderList( - folderList = (syncResponse.folders ?: emptyList()) + folderList = syncResponse + .folders + .orEmpty() .toEncryptedSdkFolderList(), ) .map { decryptedFolderList -> @@ -178,3 +207,13 @@ class VaultRepositoryImpl constructor( vaultDataMutableStateFlow.update { newState } } } + +private fun Throwable.toNetworkOrErrorState(data: T?): DataState = + if (isNoConnectionError()) { + DataState.NoNetwork(data = data) + } else { + DataState.Error( + error = this, + data = data, + ) + } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/SendData.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/SendData.kt new file mode 100644 index 0000000000..2c48b8984a --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/SendData.kt @@ -0,0 +1,12 @@ +package com.x8bit.bitwarden.data.vault.repository.model + +import com.bitwarden.core.SendView + +/** + * Represents decrypted send data. + * + * @param sendViewList List of decrypted sends. + */ +data class SendData( + val sendViewList: List, +) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt new file mode 100644 index 0000000000..024903cfca --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt @@ -0,0 +1,72 @@ +package com.x8bit.bitwarden.data.vault.repository.util + +import com.bitwarden.core.Send +import com.bitwarden.core.SendFile +import com.bitwarden.core.SendText +import com.bitwarden.core.SendType +import com.x8bit.bitwarden.data.vault.datasource.network.model.SendTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson +import java.time.ZoneOffset + +/** + * Converts a list of [SyncResponseJson.Send] objects to a list of corresponding + * Bitwarden SDK [Send] objects. + */ +fun List.toEncryptedSdkSendList(): List = + map { it.toEncryptedSdkSend() } + +/** + * Converts a [SyncResponseJson.Send] object to a corresponding + * Bitwarden SDK [Send] object. + */ +fun SyncResponseJson.Send.toEncryptedSdkSend(): Send = + Send( + id = id, + accessId = accessId.toString(), + name = name.toString(), + notes = notes, + key = key.toString(), + password = password, + type = type.toSdkSendType(), + file = file.toEncryptedSdkFile(), + text = text.toEncryptedSdkText(), + maxAccessCount = maxAccessCount?.toUInt(), + accessCount = accessCount.toUInt(), + disabled = isDisabled, + hideEmail = shouldHideEmail, + revisionDate = revisionDate.toInstant(ZoneOffset.UTC), + deletionDate = deletionDate.toInstant(ZoneOffset.UTC), + expirationDate = expirationDate?.toInstant(ZoneOffset.UTC), + ) + +/** + * Converts a [SyncResponseJson.Send.Text] object to a corresponding + * Bitwarden SDK [SendText] object. + */ +private fun SyncResponseJson.Send.Text.toEncryptedSdkText(): SendText = + SendText( + text = text, + hidden = isHidden, + ) + +/** + * Converts a [SyncResponseJson.Send.File] objects to a corresponding + * Bitwarden SDK [SendFile] object. + */ +private fun SyncResponseJson.Send.File.toEncryptedSdkFile(): SendFile = + SendFile( + id = id.toString(), + fileName = fileName.toString(), + size = size.toString(), + sizeName = sizeName.toString(), + ) + +/** + * Converts a [SendTypeJson] objects to a corresponding + * Bitwarden SDK [SendType]. + */ +private fun SendTypeJson.toSdkSendType(): SendType = + when (this) { + SendTypeJson.TEXT -> SendType.TEXT + SendTypeJson.FILE -> SendType.FILE + } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt index ce8efc38b6..c81378080d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt @@ -12,7 +12,7 @@ fun createMockSend(number: Int): SyncResponseJson.Send = type = SendTypeJson.FILE, accessId = "mockAccessId-$number", password = "mockPassword-$number", - file = createMockFile(number = 1), + file = createMockFile(number = number), deletionDate = LocalDateTime.parse("2023-10-27T12:00:00"), name = "mockName-$number", isDisabled = false, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt index 1c5f026d7f..1921ee2da3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt @@ -6,6 +6,8 @@ import com.bitwarden.core.CipherView import com.bitwarden.core.Folder import com.bitwarden.core.FolderView import com.bitwarden.core.InitCryptoRequest +import com.bitwarden.core.Send +import com.bitwarden.core.SendView import com.bitwarden.sdk.BitwardenException import com.bitwarden.sdk.ClientCrypto import com.bitwarden.sdk.ClientVault @@ -168,6 +170,53 @@ class VaultSdkSourceTest { } } + @Test + fun `decryptSendList should call SDK and return correct data wrapped in a Result`() = + runBlocking { + val mockSend = mockk() + val expectedResult = mockk() + coEvery { + clientVault.sends().decrypt( + send = mockSend, + ) + } returns expectedResult + val result = vaultSdkSource.decryptSendList( + sendList = listOf(mockSend), + ) + assertEquals( + listOf(expectedResult).asSuccess(), + result, + ) + coVerify { + clientVault.sends().decrypt( + send = mockSend, + ) + } + } + + @Test + fun `decryptSend should call SDK and return correct data wrapped in a Result`() = + runBlocking { + val mockSend = mockk() + val expectedResult = mockk() + coEvery { + clientVault.sends().decrypt( + send = mockSend, + ) + } returns expectedResult + val result = vaultSdkSource.decryptSend( + send = mockSend, + ) + assertEquals( + expectedResult.asSuccess(), result, + ) + coVerify { + clientVault.sends().decrypt( + send = mockSend, + ) + } + } + @Test fun `Folder decrypt should call SDK and return a Result with correct data`() = runBlocking { val mockFolder = mockk() diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/SendViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/SendViewUtil.kt new file mode 100644 index 0000000000..05e5206c5a --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/SendViewUtil.kt @@ -0,0 +1,51 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk.model + +import com.bitwarden.core.SendFileView +import com.bitwarden.core.SendTextView +import com.bitwarden.core.SendType +import com.bitwarden.core.SendView +import java.time.LocalDateTime +import java.time.ZoneOffset + +/** + * Create a mock [SendView] with a given [number]. + */ +fun createMockSendView(number: Int): SendView = + SendView( + id = "mockId-$number", + accessId = "mockAccessId-$number", + name = "mockName-$number", + notes = "mockNotes-$number", + key = "mockKey-$number", + password = "mockPassword-$number", + type = SendType.FILE, + file = createMockFileView(number = number), + text = createMockTextView(number = number), + maxAccessCount = 1u, + accessCount = 1u, + disabled = false, + hideEmail = false, + revisionDate = LocalDateTime.parse("2023-10-27T12:00:00").toInstant(ZoneOffset.UTC), + deletionDate = LocalDateTime.parse("2023-10-27T12:00:00").toInstant(ZoneOffset.UTC), + expirationDate = LocalDateTime.parse("2023-10-27T12:00:00").toInstant(ZoneOffset.UTC), + ) + +/** + * Create a mock [SendFileView] with a given [number]. + */ +fun createMockFileView(number: Int): SendFileView = + SendFileView( + fileName = "mockFileName-$number", + size = "1", + sizeName = "mockSizeName-$number", + id = "mockId-$number", + ) + +/** + * Create a mock [SendTextView] with a given [number]. + */ +fun createMockTextView(number: Int): SendTextView = + SendTextView( + hidden = false, + text = "mockText-$number", + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkSendUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkSendUtil.kt new file mode 100644 index 0000000000..1bff0c2bd7 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkSendUtil.kt @@ -0,0 +1,51 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk.model + +import com.bitwarden.core.Send +import com.bitwarden.core.SendFile +import com.bitwarden.core.SendText +import com.bitwarden.core.SendType +import java.time.LocalDateTime +import java.time.ZoneOffset + +/** + * Create a mock [Send] with a given [number]. + */ +fun createMockSdkSend(number: Int): Send = + Send( + id = "mockId-$number", + accessId = "mockAccessId-$number", + name = "mockName-$number", + notes = "mockNotes-$number", + key = "mockKey-$number", + password = "mockPassword-$number", + type = SendType.FILE, + file = createMockSdkFile(number = number), + text = createMockSdkText(number = number), + maxAccessCount = 1u, + accessCount = 1u, + disabled = false, + hideEmail = false, + revisionDate = LocalDateTime.parse("2023-10-27T12:00:00").toInstant(ZoneOffset.UTC), + deletionDate = LocalDateTime.parse("2023-10-27T12:00:00").toInstant(ZoneOffset.UTC), + expirationDate = LocalDateTime.parse("2023-10-27T12:00:00").toInstant(ZoneOffset.UTC), + ) + +/** + * Create a mock [SendFile] with a given [number]. + */ +fun createMockSdkFile(number: Int): SendFile = + SendFile( + fileName = "mockFileName-$number", + size = "1", + sizeName = "mockSizeName-$number", + id = "mockId-$number", + ) + +/** + * Create a mock [SendText] with a given [number]. + */ +fun createMockSdkText(number: Int): SendText = + SendText( + hidden = false, + text = "mockText-$number", + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt index 0c769203f8..f0e0d631a8 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt @@ -20,6 +20,9 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipher import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFolder +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSend +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView +import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult import io.mockk.awaits @@ -36,6 +39,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.net.UnknownHostException +@Suppress("LargeClass") class VaultRepositoryTest { private val dispatcherManager: DispatcherManager = FakeDispatcherManager() @@ -50,7 +54,7 @@ class VaultRepositoryTest { ) @Test - fun `sync with syncService Success should update AuthDiskSource and vaultDataStateFlow`() = + fun `sync with syncService Success should update AuthDiskSource and DataStateFlows`() = runTest { coEvery { syncService.sync() @@ -61,6 +65,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.userState = MOCK_USER_STATE vaultRepository.sync() @@ -82,6 +89,14 @@ class VaultRepositoryTest { ), vaultRepository.vaultDataStateFlow.value, ) + assertEquals( + DataState.Loaded( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + vaultRepository.sendDataStateFlow.value, + ) } @Test @@ -96,6 +111,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.userState = MOCK_USER_STATE vaultRepository.vaultDataStateFlow.test { @@ -135,6 +153,57 @@ class VaultRepositoryTest { } } + @Test + fun `sync with data should update sendDataStateFlow to Pending before service sync`() = + runTest { + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.sendDataStateFlow.test { + assertEquals( + DataState.Loading, + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Loaded( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Pending( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + assertEquals( + DataState.Loaded( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + } + } + @Test fun `sync with decryptCipherList Failure should update vaultDataStateFlow with Error`() = runTest { @@ -148,6 +217,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.userState = MOCK_USER_STATE vaultRepository.sync() @@ -171,6 +243,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns mockException.asFailure() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.userState = MOCK_USER_STATE vaultRepository.sync() @@ -182,7 +257,33 @@ class VaultRepositoryTest { } @Test - fun `sync with syncService Failure should update vaultDataStateFlow with an Error`() = + fun `sync with decryptSendList Failure should update sendDataStateFlow with Error`() = + runTest { + val mockException = IllegalStateException() + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns mockException.asFailure() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.sync() + + assertEquals( + DataState.Error(error = mockException), + vaultRepository.sendDataStateFlow.value, + ) + } + + @Test + fun `sync with syncService Failure should update vault and send DataStateFlow with an Error`() = runTest { val mockException = IllegalStateException( "sad", @@ -200,10 +301,17 @@ class VaultRepositoryTest { ), vaultRepository.vaultDataStateFlow.value, ) + assertEquals( + DataState.Error( + error = mockException, + data = null, + ), + vaultRepository.sendDataStateFlow.value, + ) } @Test - fun `sync with NoNetwork should update vaultDataStateFlow to NoNetwork`() = + fun `sync with NoNetwork should update vault and send DataStateFlow to NoNetwork`() = runTest { coEvery { syncService.sync() @@ -217,6 +325,12 @@ class VaultRepositoryTest { ), vaultRepository.vaultDataStateFlow.value, ) + assertEquals( + DataState.NoNetwork( + data = null, + ), + vaultRepository.sendDataStateFlow.value, + ) } @Test @@ -231,6 +345,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.userState = MOCK_USER_STATE vaultRepository.vaultDataStateFlow.test { @@ -273,6 +390,60 @@ class VaultRepositoryTest { } } + @Test + fun `sync with NoNetwork data should update sendDataStateFlow to NoNetwork with data`() = + runTest { + coEvery { + syncService.sync() + } returnsMany listOf( + Result.success(createMockSyncResponse(number = 1)), + UnknownHostException().asFailure(), + ) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.sendDataStateFlow.test { + assertEquals( + DataState.Loading, + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Loaded( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Pending( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + assertEquals( + DataState.NoNetwork( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + } + } + @Test fun `unlockVaultAndSync with initializeCrypto Success should sync and return Success`() = runTest { @@ -285,6 +456,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.storePrivateKey( userId = "mockUserId", privateKey = "mockPrivateKey-1", @@ -327,6 +501,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.storePrivateKey( userId = "mockUserId", privateKey = "mockPrivateKey-1", @@ -540,7 +717,7 @@ class VaultRepositoryTest { } @Test - fun `clearVaultData should update the vaultDataStateFlow to Loading`() = + fun `clearUnlockedData should update the vaultDataStateFlow to Loading`() = runTest { coEvery { syncService.sync() @@ -551,6 +728,9 @@ class VaultRepositoryTest { coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() fakeAuthDiskSource.userState = MOCK_USER_STATE vaultRepository.vaultDataStateFlow.test { @@ -569,7 +749,48 @@ class VaultRepositoryTest { awaitItem(), ) - vaultRepository.clearVaultData() + vaultRepository.clearUnlockedData() + + assertEquals( + DataState.Loading, + awaitItem(), + ) + } + } + + @Test + fun `clearUnlockedData should update the sendDataStateFlow to Loading`() = + runTest { + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1))) + } returns listOf(createMockSendView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.sendDataStateFlow.test { + assertEquals( + DataState.Loading, + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Loaded( + data = SendData( + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + awaitItem(), + ) + + vaultRepository.clearUnlockedData() assertEquals( DataState.Loading, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensionsTest.kt new file mode 100644 index 0000000000..0fb01bccc6 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensionsTest.kt @@ -0,0 +1,36 @@ +package com.x8bit.bitwarden.data.vault.repository.util + +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSend +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class VaultSdkSendExtensionsTest { + + @Test + fun `toEncryptedSdkSend should convert a network-based Send to SDK-based Send`() { + val syncSend = createMockSend(number = 1) + val sdkSend = syncSend.toEncryptedSdkSend() + assertEquals( + createMockSdkSend(number = 1), + sdkSend, + ) + } + + @Test + @Suppress("MaxLineLength") + fun `toEncryptedSdkSendList should convert list of network-based Send to List of SDK-based Send`() { + val syncSends = listOf( + createMockSend(number = 1), + createMockSend(number = 2), + ) + val sdkSends = syncSends.toEncryptedSdkSendList() + assertEquals( + listOf( + createMockSdkSend(number = 1), + createMockSdkSend(number = 2), + ), + sdkSends, + ) + } +}