From a43a54171939b465a5cf1649ec0d8155097cc672 Mon Sep 17 00:00:00 2001 From: Ramsey Smith <142836716+ramsey-livefront@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:16:06 -0700 Subject: [PATCH] BIT-1128: Expose vault data (#227) --- .../datasource/network/util/NetworkUtils.kt | 9 + .../platform/repository/model/DataState.kt | 48 +++ .../data/vault/repository/VaultRepository.kt | 13 + .../vault/repository/VaultRepositoryImpl.kt | 83 ++++- .../data/vault/repository/model/VaultData.kt | 15 + .../network/util/NetworkUtilsTest.kt | 17 + .../sdk/model/CipherListViewUtil.kt | 35 +++ .../datasource/sdk/model/FolderViewUtil.kt | 17 + .../sdk/{ => model}/VaultSdkCipherUtil.kt | 2 +- .../sdk/{ => model}/VaultSdkFolderUtil.kt | 2 +- .../vault/repository/VaultRepositoryTest.kt | 294 ++++++++++++++++-- .../util/VaultSdkCipherExtensionsTest.kt | 18 +- .../util/VaultSdkFolderExtensionsTest.kt | 2 +- 13 files changed, 507 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/repository/model/DataState.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherListViewUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/FolderViewUtil.kt rename app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/{ => model}/VaultSdkCipherUtil.kt (98%) rename app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/{ => model}/VaultSdkFolderUtil.kt (86%) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtils.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtils.kt index 6962bd22e7..b384cdaa4d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtils.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtils.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.util import okio.ByteString.Companion.decodeBase64 +import java.net.UnknownHostException import java.nio.charset.Charset import java.util.Base64 @@ -32,3 +33,11 @@ fun String.base64UrlDecodeOrNull(): String? = .replace("_", "/") .decodeBase64() ?.string(Charset.defaultCharset()) + +/** + * Returns true if the throwable represents a no network error. + */ +fun Throwable?.isNoConnectionError(): Boolean { + return this is UnknownHostException || + this?.cause?.isNoConnectionError() ?: false +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/model/DataState.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/model/DataState.kt new file mode 100644 index 0000000000..5f3867eca3 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/model/DataState.kt @@ -0,0 +1,48 @@ +package com.x8bit.bitwarden.data.platform.repository.model + +/** + * A data state that can be used as a template for data in the repository layer. + */ +sealed class DataState { + + /** + * Data that is being wrapped by [DataState]. + */ + abstract val data: T? + + /** + * Loading state that has no data is available. + */ + data object Loading : DataState() { + override val data: Nothing? get() = null + } + + /** + * Loaded state that has data available. + */ + data class Loaded( + override val data: T, + ) : DataState() + + /** + * Pending state that has data available. + */ + data class Pending( + override val data: T, + ) : DataState() + + /** + * Error state that may have data available. + */ + data class Error( + val error: Throwable, + override val data: T? = null, + ) : DataState() + + /** + * No network state that may have data is available. + */ + data class NoNetwork( + override val data: T? = null, + ) : DataState() +} 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 7f5f05b6f6..d3f27e4f74 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,12 +1,25 @@ package com.x8bit.bitwarden.data.vault.repository +import com.x8bit.bitwarden.data.platform.repository.model.DataState +import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult +import kotlinx.coroutines.flow.StateFlow /** * Responsible for managing vault data inside the network layer. */ interface VaultRepository { + /** + * Flow that represents the current vault data. + */ + val vaultDataStateFlow: StateFlow> + + /** + * Clear the in memory vault data. + */ + fun clearVaultData() + /** * 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 28c0ac26a2..5bf22129cc 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 @@ -3,15 +3,24 @@ package com.x8bit.bitwarden.data.vault.repository import com.bitwarden.core.InitCryptoRequest import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams +import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.platform.repository.model.DataState +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.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.toVaultUnlockResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch /** @@ -28,8 +37,23 @@ class VaultRepositoryImpl constructor( private var syncJob: Job = Job().apply { complete() } + private val vaultDataMutableStateFlow = + MutableStateFlow>(DataState.Loading) + + override val vaultDataStateFlow: StateFlow> + get() = vaultDataMutableStateFlow.asStateFlow() + + override fun clearVaultData() { + vaultDataMutableStateFlow.update { DataState.Loading } + } + override fun sync() { if (!syncJob.isCompleted) return + vaultDataMutableStateFlow.value.data?.let { data -> + vaultDataMutableStateFlow.update { + DataState.Pending(data = data) + } + } syncJob = scope.launch { syncService .sync() @@ -39,20 +63,23 @@ class VaultRepositoryImpl constructor( userKey = syncResponse.profile?.key, privateKey = syncResponse.profile?.privateKey, ) - // TODO transform into domain object consumable by VaultViewModel BIT-205. - syncResponse.ciphers?.let { networkCiphers -> - vaultSdkSource.decryptCipherList( - cipherList = networkCiphers.toEncryptedSdkCipherList(), - ) - } - syncResponse.folders?.let { networkFolders -> - vaultSdkSource.decryptFolderList( - folderList = networkFolders.toEncryptedSdkFolderList(), - ) - } + decryptSyncResponseAndUpdateVaultDataState( + syncResponse = syncResponse, + ) }, - onFailure = { - // TODO handle failure BIT-205. + onFailure = { throwable -> + vaultDataMutableStateFlow.update { + if (throwable.isNoConnectionError()) { + DataState.NoNetwork( + data = it.data, + ) + } else { + DataState.Error( + error = throwable, + data = it.data, + ) + } + } }, ) } @@ -108,4 +135,34 @@ class VaultRepositoryImpl constructor( onSuccess = { it.toVaultUnlockResult() }, ) } + + private suspend fun decryptSyncResponseAndUpdateVaultDataState(syncResponse: SyncResponseJson) { + val newState = vaultSdkSource + .decryptCipherList( + cipherList = (syncResponse.ciphers ?: emptyList()) + .toEncryptedSdkCipherList(), + ) + .flatMap { decryptedCipherList -> + vaultSdkSource + .decryptFolderList( + folderList = (syncResponse.folders ?: emptyList()) + .toEncryptedSdkFolderList(), + ) + .map { decryptedFolderList -> + decryptedCipherList to decryptedFolderList + } + } + .fold( + onSuccess = { (decryptedCipherList, decryptedFolderList) -> + DataState.Loaded( + data = VaultData( + cipherListViewList = decryptedCipherList, + folderViewList = decryptedFolderList, + ), + ) + }, + onFailure = { DataState.Error(error = it) }, + ) + vaultDataMutableStateFlow.update { newState } + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt new file mode 100644 index 0000000000..e92504a7be --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt @@ -0,0 +1,15 @@ +package com.x8bit.bitwarden.data.vault.repository.model + +import com.bitwarden.core.CipherListView +import com.bitwarden.core.FolderView + +/** + * Represents decrypted vault data. + * + * @param cipherListViewList List of decrypted ciphers. + * @param folderViewList List of decrypted folders. + */ +data class VaultData( + val cipherListViewList: List, + val folderViewList: List, +) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtilsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtilsTest.kt index 48f2b8d4ca..9eba24c99c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtilsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/network/util/NetworkUtilsTest.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.util import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test +import java.net.UnknownHostException class NetworkUtilsTest { @Test @@ -40,4 +41,20 @@ class NetworkUtilsTest { "*.*".base64UrlDecodeOrNull(), ) } + + @Test + fun `isNoConnectionError should return return true for UnknownHostException`() { + assertEquals( + true, + UnknownHostException().isNoConnectionError(), + ) + } + + @Test + fun `isNoConnectionError should return return false for not UnknownHostException`() { + assertEquals( + false, + IllegalStateException().isNoConnectionError(), + ) + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherListViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherListViewUtil.kt new file mode 100644 index 0000000000..3c537e4334 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherListViewUtil.kt @@ -0,0 +1,35 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk.model + +import com.bitwarden.core.CipherListView +import com.bitwarden.core.CipherRepromptType +import com.bitwarden.core.CipherType +import java.time.LocalDateTime +import java.time.ZoneOffset + +/** + * Create a mock [CipherListView] with a given [number]. + */ +fun createMockCipherListView(number: Int): CipherListView = + CipherListView( + id = "mockId-$number", + organizationId = "mockOrganizationId-$number", + folderId = "mockFolderId-$number", + collectionIds = listOf("mockCollectionId-$number"), + name = "mockName-$number", + type = CipherType.LOGIN, + creationDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + deletedDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + revisionDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + attachments = 1U, + favorite = false, + reprompt = CipherRepromptType.NONE, + edit = false, + viewPassword = false, + subTitle = "", + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/FolderViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/FolderViewUtil.kt new file mode 100644 index 0000000000..826392f8f3 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/FolderViewUtil.kt @@ -0,0 +1,17 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk.model + +import com.bitwarden.core.FolderView +import java.time.LocalDateTime +import java.time.ZoneOffset + +/** + * Create a mock [FolderView] with a given [number]. + */ +fun createMockFolderView(number: Int): FolderView = + FolderView( + id = "mockId-$number", + name = "mockName-$number", + revisionDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt similarity index 98% rename from app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt rename to app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt index 8eaba496bd..b8be34a1cf 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.data.vault.datasource.sdk +package com.x8bit.bitwarden.data.vault.datasource.sdk.model import com.bitwarden.core.Attachment import com.bitwarden.core.Card diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkFolderUtil.kt similarity index 86% rename from app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt rename to app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkFolderUtil.kt index 682c184af3..1026781e49 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkFolderUtil.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.data.vault.datasource.sdk +package com.x8bit.bitwarden.data.vault.datasource.sdk.model import com.bitwarden.core.Folder import java.time.LocalDateTime 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 cc6269250c..86db11af18 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 @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.vault.repository +import app.cash.turbine.test import com.bitwarden.core.InitCryptoRequest import com.bitwarden.core.Kdf import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson @@ -8,12 +9,18 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.platform.repository.model.DataState +import com.x8bit.bitwarden.data.platform.util.asFailure +import com.x8bit.bitwarden.data.platform.util.asSuccess import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSyncResponse import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCipher -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkFolder +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.createMockCipherListView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView +import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult import io.mockk.coEvery import io.mockk.coVerify @@ -21,6 +28,7 @@ import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import java.net.UnknownHostException class VaultRepositoryTest { @@ -36,27 +44,228 @@ class VaultRepositoryTest { ) @Test - fun `sync when syncService Success should update AuthDiskSource with keys`() = runTest { - coEvery { syncService.sync() } returns Result.success(createMockSyncResponse(number = 1)) - coEvery { - vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) - } returns mockk() - coEvery { - vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) - } returns mockk() - fakeAuthDiskSource.userState = MOCK_USER_STATE + fun `sync with syncService Success should update AuthDiskSource and vaultDataStateFlow`() = + runTest { + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherListView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE - vaultRepository.sync() + vaultRepository.sync() - fakeAuthDiskSource.assertUserKey( - userId = "mockUserId", - userKey = "mockKey-1", - ) - fakeAuthDiskSource.assertPrivateKey( - userId = "mockUserId", - privateKey = "mockPrivateKey-1", - ) - } + fakeAuthDiskSource.assertUserKey( + userId = "mockUserId", + userKey = "mockKey-1", + ) + fakeAuthDiskSource.assertPrivateKey( + userId = "mockUserId", + privateKey = "mockPrivateKey-1", + ) + assertEquals( + DataState.Loaded( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + vaultRepository.vaultDataStateFlow.value, + ) + } + + @Test + fun `sync with data should update vaultDataStateFlow to Pending before service sync`() = + runTest { + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherListView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.vaultDataStateFlow.test { + assertEquals( + DataState.Loading, + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Loaded( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Pending( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + assertEquals( + DataState.Loaded( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + } + } + + @Test + fun `sync with decryptCipherList Failure should update vaultDataStateFlow with Error`() = + runTest { + val mockException = IllegalStateException() + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns mockException.asFailure() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.sync() + + assertEquals( + DataState.Error(error = mockException), + vaultRepository.vaultDataStateFlow.value, + ) + } + + @Test + fun `sync with decryptFolderList Failure should update vaultDataStateFlow with Error`() = + runTest { + val mockException = IllegalStateException() + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherListView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns mockException.asFailure() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.sync() + + assertEquals( + DataState.Error(error = mockException), + vaultRepository.vaultDataStateFlow.value, + ) + } + + @Test + fun `sync with syncService Failure should update vaultDataStateFlow with an Error`() = + runTest { + val mockException = IllegalStateException( + "sad", + ) + coEvery { + syncService.sync() + } returns mockException.asFailure() + + vaultRepository.sync() + + assertEquals( + DataState.Error( + error = mockException, + data = null, + ), + vaultRepository.vaultDataStateFlow.value, + ) + } + + @Test + fun `sync with NoNetwork should update vaultDataStateFlow to NoNetwork`() = + runTest { + coEvery { + syncService.sync() + } returns UnknownHostException().asFailure() + + vaultRepository.sync() + + assertEquals( + DataState.NoNetwork( + data = null, + ), + vaultRepository.vaultDataStateFlow.value, + ) + } + + @Test + fun `sync with NoNetwork data should update vaultDataStateFlow to NoNetwork with data`() = + runTest { + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherListView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.vaultDataStateFlow.test { + assertEquals( + DataState.Loading, + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Loaded( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + coEvery { + syncService.sync() + } returns UnknownHostException().asFailure() + vaultRepository.sync() + assertEquals( + DataState.Pending( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + assertEquals( + DataState.NoNetwork( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + } + } @Test fun `unlockVaultAndSync with initializeCrypto Success should sync and return Success`() = @@ -66,10 +275,10 @@ class VaultRepositoryTest { } returns Result.success(createMockSyncResponse(number = 1)) coEvery { vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) - } returns mockk() + } returns listOf(createMockCipherListView(number = 1)).asSuccess() coEvery { vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) - } returns mockk() + } returns listOf(createMockFolderView(number = 1)).asSuccess() fakeAuthDiskSource.storePrivateKey( userId = "mockUserId", privateKey = "mockPrivateKey-1", @@ -233,6 +442,45 @@ class VaultRepositoryTest { result, ) } + + @Test + fun `clearVaultData should update the vaultDataStateFlow to Loading`() = + runTest { + coEvery { + syncService.sync() + } returns Result.success(createMockSyncResponse(number = 1)) + coEvery { + vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1))) + } returns listOf(createMockCipherListView(number = 1)).asSuccess() + coEvery { + vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1))) + } returns listOf(createMockFolderView(number = 1)).asSuccess() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + vaultRepository.vaultDataStateFlow.test { + assertEquals( + DataState.Loading, + awaitItem(), + ) + vaultRepository.sync() + assertEquals( + DataState.Loaded( + data = VaultData( + cipherListViewList = listOf(createMockCipherListView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + ), + ), + awaitItem(), + ) + + vaultRepository.clearVaultData() + + assertEquals( + DataState.Loading, + awaitItem(), + ) + } + } } private val MOCK_USER_STATE = UserStateJson( diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt index 908688c6c9..abaa7b7996 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt @@ -17,15 +17,15 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockLogin import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPasswordHistory import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSecureNote import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockUri -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkAttachment -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCard -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCipher -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkField -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkIdentity -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkLogin -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkPasswordHistory -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkSecureNote -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkUri +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkAttachment +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCard +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipher +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkField +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkIdentity +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkLogin +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkPasswordHistory +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSecureNote +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkUri import org.junit.Assert.assertEquals import org.junit.Test diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt index 4e5b9ce600..83bda5f074 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.util import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder -import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkFolder +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFolder import org.junit.Assert.assertEquals import org.junit.Test