BIT-474, BIT-518, BIT-519, BIT-521: Expose flows from the vault database tables (#414)

This commit is contained in:
David Perez 2023-12-19 11:08:16 -06:00 committed by Álison Fernandes
parent 8a2a205247
commit abe2354c15
4 changed files with 495 additions and 74 deletions

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.vault.repository
import com.bitwarden.core.CipherView
import com.bitwarden.core.CollectionView
import com.bitwarden.core.FolderView
import com.bitwarden.core.Kdf
import com.x8bit.bitwarden.data.platform.repository.model.DataState
@ -22,6 +23,21 @@ interface VaultRepository {
*/
val vaultDataStateFlow: StateFlow<DataState<VaultData>>
/**
* Flow that represents all ciphers for the active user.
*/
val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
/**
* Flow that represents all collections for the active user.
*/
val collectionsStateFlow: StateFlow<DataState<List<CollectionView>>>
/**
* Flow that represents all folders for the active user.
*/
val foldersStateFlow: StateFlow<DataState<List<FolderView>>>
/**
* Flow that represents the current vault state.
*/

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.vault.repository
import com.bitwarden.core.CipherView
import com.bitwarden.core.CollectionView
import com.bitwarden.core.FolderView
import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod
@ -13,10 +14,12 @@ import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionE
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.map
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn
import com.x8bit.bitwarden.data.platform.repository.util.updateToPendingOrLoading
import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.platform.util.flatMap
import com.x8bit.bitwarden.data.platform.util.zip
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
@ -36,14 +39,19 @@ 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
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -53,9 +61,10 @@ import kotlinx.coroutines.withContext
* Default implementation of [VaultRepository].
*/
@Suppress("TooManyFunctions")
class VaultRepositoryImpl constructor(
class VaultRepositoryImpl(
private val syncService: SyncService,
private val ciphersService: CiphersService,
private val vaultDiskSource: VaultDiskSource,
private val vaultSdkSource: VaultSdkSource,
private val authDiskSource: AuthDiskSource,
private val dispatcherManager: DispatcherManager,
@ -67,6 +76,8 @@ class VaultRepositoryImpl constructor(
private var willSyncAfterUnlock = false
private val activeUserId: String? get() = authDiskSource.userState?.activeUserId
private val vaultDataMutableStateFlow =
MutableStateFlow<DataState<VaultData>>(DataState.Loading)
@ -79,19 +90,65 @@ class VaultRepositoryImpl constructor(
override val vaultDataStateFlow: StateFlow<DataState<VaultData>>
get() = vaultDataMutableStateFlow.asStateFlow()
override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
get() = mutableCiphersStateFlow.asStateFlow()
override val foldersStateFlow: StateFlow<DataState<List<FolderView>>>
get() = mutableFoldersStateFlow.asStateFlow()
override val collectionsStateFlow: StateFlow<DataState<List<CollectionView>>>
get() = mutableCollectionsStateFlow.asStateFlow()
override val vaultStateFlow: StateFlow<VaultState>
get() = vaultMutableStateFlow.asStateFlow()
override val sendDataStateFlow: StateFlow<DataState<SendData>>
get() = sendDataMutableStateFlow.asStateFlow()
private val mutableCiphersStateFlow =
MutableStateFlow<DataState<List<CipherView>>>(DataState.Loading)
private val mutableFoldersStateFlow =
MutableStateFlow<DataState<List<FolderView>>>(DataState.Loading)
private val mutableCollectionsStateFlow =
MutableStateFlow<DataState<List<CollectionView>>>(DataState.Loading)
init {
// Setup ciphers MutableStateFlow
mutableCiphersStateFlow
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
observeVaultDiskCiphers(activeUserId)
}
.launchIn(scope)
// Setup folders MutableStateFlow
mutableFoldersStateFlow
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
observeVaultDiskFolders(activeUserId)
}
.launchIn(scope)
// Setup collections MutableStateFlow
mutableCollectionsStateFlow
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
observeVaultDiskCollections(activeUserId)
}
.launchIn(scope)
}
override fun clearUnlockedData() {
mutableCiphersStateFlow.update { DataState.Loading }
mutableFoldersStateFlow.update { DataState.Loading }
mutableCollectionsStateFlow.update { DataState.Loading }
vaultDataMutableStateFlow.update { DataState.Loading }
sendDataMutableStateFlow.update { DataState.Loading }
}
override fun sync() {
if (!syncJob.isCompleted || willSyncAfterUnlock) return
val userId = activeUserId ?: return
mutableCiphersStateFlow.updateToPendingOrLoading()
mutableFoldersStateFlow.updateToPendingOrLoading()
mutableCollectionsStateFlow.updateToPendingOrLoading()
vaultDataMutableStateFlow.updateToPendingOrLoading()
sendDataMutableStateFlow.updateToPendingOrLoading()
syncJob = scope.launch {
@ -108,10 +165,28 @@ class VaultRepositoryImpl constructor(
unlockVaultForOrganizationsIfNecessary(syncResponse = syncResponse)
storeKeys(syncResponse = syncResponse)
decryptSyncResponseAndUpdateVaultDataState(syncResponse = syncResponse)
decryptSyncResponseAndUpdateVaultDataState(
userId = userId,
syncResponse = syncResponse,
)
decryptSendsAndUpdateSendDataState(sendList = syncResponse.sends)
},
onFailure = { throwable ->
mutableCiphersStateFlow.update { currentState ->
throwable.toNetworkOrErrorState(
data = currentState.data,
)
}
mutableFoldersStateFlow.update { currentState ->
throwable.toNetworkOrErrorState(
data = currentState.data,
)
}
mutableCollectionsStateFlow.update { currentState ->
throwable.toNetworkOrErrorState(
data = currentState.data,
)
}
vaultDataMutableStateFlow.update { currentState ->
throwable.toNetworkOrErrorState(
data = currentState.data,
@ -371,8 +446,13 @@ class VaultRepositoryImpl constructor(
}
private suspend fun decryptSyncResponseAndUpdateVaultDataState(
userId: String,
syncResponse: SyncResponseJson,
) = withContext(dispatcherManager.default) {
val deferred = async {
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
}
// Allow decryption of various types in parallel.
val newState = zip(
{
@ -414,7 +494,56 @@ class VaultRepositoryImpl constructor(
onFailure = { DataState.Error(error = it) },
)
vaultDataMutableStateFlow.update { newState }
deferred.await()
}
private fun observeVaultDiskCiphers(
userId: String,
): Flow<DataState<List<CipherView>>> =
vaultDiskSource
.getCiphers(userId = userId)
.onStart { mutableCiphersStateFlow.value = DataState.Loading }
.map {
vaultSdkSource
.decryptCipherList(cipherList = it.toEncryptedSdkCipherList())
.fold(
onSuccess = { ciphers -> DataState.Loaded(ciphers) },
onFailure = { throwable -> DataState.Error(throwable) },
)
}
.onEach { mutableCiphersStateFlow.value = it }
private fun observeVaultDiskFolders(
userId: String,
): Flow<DataState<List<FolderView>>> =
vaultDiskSource
.getFolders(userId = userId)
.onStart { mutableFoldersStateFlow.value = DataState.Loading }
.map {
vaultSdkSource
.decryptFolderList(folderList = it.toEncryptedSdkFolderList())
.fold(
onSuccess = { folders -> DataState.Loaded(folders) },
onFailure = { throwable -> DataState.Error(throwable) },
)
}
.onEach { mutableFoldersStateFlow.value = it }
private fun observeVaultDiskCollections(
userId: String,
): Flow<DataState<List<CollectionView>>> =
vaultDiskSource
.getCollections(userId = userId)
.onStart { mutableCollectionsStateFlow.value = DataState.Loading }
.map {
vaultSdkSource
.decryptCollectionList(collectionList = it.toEncryptedSdkCollectionList())
.fold(
onSuccess = { collections -> DataState.Loaded(collections) },
onFailure = { throwable -> DataState.Error(throwable) },
)
}
.onEach { mutableCollectionsStateFlow.value = it }
}
private fun <T> Throwable.toNetworkOrErrorState(data: T?): DataState<T> =

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository.di
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
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.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@ -25,12 +26,14 @@ object VaultRepositoryModule {
fun providesVaultRepository(
syncService: SyncService,
ciphersService: CiphersService,
vaultDiskSource: VaultDiskSource,
vaultSdkSource: VaultSdkSource,
authDiskSource: AuthDiskSource,
dispatcherManager: DispatcherManager,
): VaultRepository = VaultRepositoryImpl(
syncService = syncService,
ciphersService = ciphersService,
vaultDiskSource = vaultDiskSource,
vaultSdkSource = vaultSdkSource,
authDiskSource = authDiskSource,
dispatcherManager = dispatcherManager,

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository
import app.cash.turbine.test
import com.bitwarden.core.CipherView
import com.bitwarden.core.CollectionView
import com.bitwarden.core.FolderView
import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod
@ -17,8 +18,12 @@ 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.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCollection
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganizationKeys
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSyncResponse
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
@ -39,14 +44,20 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
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.toEncryptedSdkCollectionList
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
import io.mockk.awaits
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
@ -60,22 +71,185 @@ class VaultRepositoryTest {
private val fakeAuthDiskSource = FakeAuthDiskSource()
private val syncService: SyncService = mockk()
private val ciphersService: CiphersService = mockk()
private val vaultDiskSource: VaultDiskSource = mockk()
private val vaultSdkSource: VaultSdkSource = mockk()
private val vaultRepository = VaultRepositoryImpl(
syncService = syncService,
ciphersService = ciphersService,
vaultDiskSource = vaultDiskSource,
vaultSdkSource = vaultSdkSource,
authDiskSource = fakeAuthDiskSource,
dispatcherManager = dispatcherManager,
)
@Test
fun `ciphersStateFlow should emit decrypted list of ciphers when decryptCipherList succeeds`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockCipherList = listOf(createMockCipher(number = 1))
val mockEncryptedCipherList = mockCipherList.toEncryptedSdkCipherList()
val mockCipherViewList = listOf(createMockCipherView(number = 1))
val mutableCiphersStateFlow = MutableSharedFlow<List<SyncResponseJson.Cipher>>(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
)
every {
vaultDiskSource.getCiphers(userId = MOCK_USER_STATE.activeUserId)
} returns mutableCiphersStateFlow
coEvery {
vaultSdkSource.decryptCipherList(mockEncryptedCipherList)
} returns mockCipherViewList.asSuccess()
vaultRepository
.ciphersStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableCiphersStateFlow.tryEmit(mockCipherList)
assertEquals(DataState.Loaded(mockCipherViewList), awaitItem())
}
}
@Test
fun `ciphersStateFlow should emit an error when decryptCipherList fails`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val throwable = Throwable("Fail")
val mockCipherList = listOf(createMockCipher(number = 1))
val mockEncryptedCipherList = mockCipherList.toEncryptedSdkCipherList()
val mutableCiphersStateFlow = MutableSharedFlow<List<SyncResponseJson.Cipher>>(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
)
every {
vaultDiskSource.getCiphers(userId = MOCK_USER_STATE.activeUserId)
} returns mutableCiphersStateFlow
coEvery {
vaultSdkSource.decryptCipherList(mockEncryptedCipherList)
} returns throwable.asFailure()
vaultRepository
.ciphersStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableCiphersStateFlow.tryEmit(mockCipherList)
assertEquals(DataState.Error<List<CipherView>>(throwable), awaitItem())
}
}
@Suppress("MaxLineLength")
@Test
fun `collectionsStateFlow should emit decrypted list of collections when decryptCollectionList succeeds`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockCollectionList = listOf(createMockCollection(number = 1))
val mockEncryptedCollectionList = mockCollectionList.toEncryptedSdkCollectionList()
val mockCollectionViewList = listOf(createMockCollectionView(number = 1))
val mutableCollectionsStateFlow = MutableSharedFlow<List<SyncResponseJson.Collection>>(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
)
every {
vaultDiskSource.getCollections(userId = MOCK_USER_STATE.activeUserId)
} returns mutableCollectionsStateFlow
coEvery {
vaultSdkSource.decryptCollectionList(mockEncryptedCollectionList)
} returns mockCollectionViewList.asSuccess()
vaultRepository
.collectionsStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableCollectionsStateFlow.tryEmit(mockCollectionList)
assertEquals(DataState.Loaded(mockCollectionViewList), awaitItem())
}
}
@Test
fun `collectionsStateFlow should emit an error when decryptCollectionList fails`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val throwable = Throwable("Fail")
val mockCollectionList = listOf(createMockCollection(number = 1))
val mockEncryptedCollectionList = mockCollectionList.toEncryptedSdkCollectionList()
val mutableCollectionStateFlow = MutableSharedFlow<List<SyncResponseJson.Collection>>(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
)
every {
vaultDiskSource.getCollections(userId = MOCK_USER_STATE.activeUserId)
} returns mutableCollectionStateFlow
coEvery {
vaultSdkSource.decryptCollectionList(mockEncryptedCollectionList)
} returns throwable.asFailure()
vaultRepository
.collectionsStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableCollectionStateFlow.tryEmit(mockCollectionList)
assertEquals(DataState.Error<List<CollectionView>>(throwable), awaitItem())
}
}
@Suppress("MaxLineLength")
@Test
fun `foldersStateFlow should emit decrypted list of folders when decryptFolderList succeeds`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockFolderList = listOf(createMockFolder(number = 1))
val mockEncryptedFolderList = mockFolderList.toEncryptedSdkFolderList()
val mockFolderViewList = listOf(createMockFolderView(number = 1))
val mutableFoldersStateFlow = MutableSharedFlow<List<SyncResponseJson.Folder>>(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
)
every {
vaultDiskSource.getFolders(userId = MOCK_USER_STATE.activeUserId)
} returns mutableFoldersStateFlow
coEvery {
vaultSdkSource.decryptFolderList(mockEncryptedFolderList)
} returns mockFolderViewList.asSuccess()
vaultRepository
.foldersStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableFoldersStateFlow.tryEmit(mockFolderList)
assertEquals(DataState.Loaded(mockFolderViewList), awaitItem())
}
}
@Test
fun `foldersStateFlow should emit an error when decryptFolderList fails`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val throwable = Throwable("Fail")
val mockFolderList = listOf(createMockFolder(number = 1))
val mockEncryptedFolderList = mockFolderList.toEncryptedSdkFolderList()
val mutableFoldersStateFlow = MutableSharedFlow<List<SyncResponseJson.Folder>>(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
)
every {
vaultDiskSource.getFolders(userId = MOCK_USER_STATE.activeUserId)
} returns mutableFoldersStateFlow
coEvery {
vaultSdkSource.decryptFolderList(mockEncryptedFolderList)
} returns throwable.asFailure()
vaultRepository
.foldersStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableFoldersStateFlow.tryEmit(mockFolderList)
assertEquals(DataState.Error<List<FolderView>>(throwable), awaitItem())
}
}
@Suppress("MaxLineLength")
@Test
fun `sync with syncService Success should unlock the vault for orgs if necessary and update AuthDiskSource and DataStateFlows`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -83,6 +257,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -155,9 +335,9 @@ class VaultRepositoryTest {
@Test
fun `sync with data should update vaultDataStateFlow to Pending before service sync`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -165,6 +345,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -177,7 +363,6 @@ class VaultRepositoryTest {
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
vaultRepository.vaultDataStateFlow.test {
assertEquals(
@ -222,9 +407,8 @@ 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))
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -232,6 +416,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -283,10 +473,10 @@ class VaultRepositoryTest {
@Test
fun `sync with decryptCipherList Failure should update vaultDataStateFlow with Error`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockException = IllegalStateException()
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -294,6 +484,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns mockException.asFailure()
@ -320,9 +516,9 @@ class VaultRepositoryTest {
fun `sync with decryptFolderList Failure should update vaultDataStateFlow with Error`() =
runTest {
val mockException = IllegalStateException()
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
val mockSyncResponse = createMockSyncResponse(number = 1)
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -330,6 +526,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -356,9 +558,9 @@ class VaultRepositoryTest {
fun `sync with decryptCollectionList Failure should update vaultDataStateFlow with Error`() =
runTest {
val mockException = IllegalStateException()
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -366,6 +568,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -378,7 +586,6 @@ class VaultRepositoryTest {
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
vaultRepository.sync()
@ -392,9 +599,9 @@ class VaultRepositoryTest {
fun `sync with decryptSendList Failure should update sendDataStateFlow with Error`() =
runTest {
val mockException = IllegalStateException()
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -402,6 +609,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -427,6 +640,7 @@ class VaultRepositoryTest {
@Test
fun `sync with syncService Failure should update vault and send DataStateFlow with an Error`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockException = IllegalStateException(
"sad",
)
@ -453,34 +667,32 @@ class VaultRepositoryTest {
}
@Test
fun `sync with NoNetwork should update vault and send DataStateFlow to NoNetwork`() =
runTest {
coEvery {
syncService.sync()
} returns UnknownHostException().asFailure()
fun `sync with NoNetwork should update vault and send DataStateFlow to NoNetwork`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery { syncService.sync() } returns UnknownHostException().asFailure()
vaultRepository.sync()
vaultRepository.sync()
assertEquals(
DataState.NoNetwork(
data = null,
),
vaultRepository.vaultDataStateFlow.value,
)
assertEquals(
DataState.NoNetwork(
data = null,
),
vaultRepository.sendDataStateFlow.value,
)
}
assertEquals(
DataState.NoNetwork(
data = null,
),
vaultRepository.vaultDataStateFlow.value,
)
assertEquals(
DataState.NoNetwork(
data = null,
),
vaultRepository.sendDataStateFlow.value,
)
}
@Test
fun `sync with NoNetwork data should update vaultDataStateFlow to NoNetwork with data`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -488,6 +700,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -500,7 +718,6 @@ class VaultRepositoryTest {
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
vaultRepository.vaultDataStateFlow.test {
assertEquals(
@ -548,10 +765,12 @@ class VaultRepositoryTest {
@Test
fun `sync with NoNetwork data should update sendDataStateFlow to NoNetwork with data`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery {
syncService.sync()
} returnsMany listOf(
Result.success(createMockSyncResponse(number = 1)),
mockSyncResponse.asSuccess(),
UnknownHostException().asFailure(),
)
coEvery {
@ -561,6 +780,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -573,7 +798,6 @@ class VaultRepositoryTest {
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
vaultRepository.sendDataStateFlow.test {
assertEquals(
@ -661,9 +885,8 @@ class VaultRepositoryTest {
@Test
fun `unlockVaultAndSyncForCurrentUser with unlockVault Success should sync and return Success`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -671,6 +894,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -736,9 +965,8 @@ class VaultRepositoryTest {
@Test
fun `sync should be able to be called after unlockVaultAndSyncForCurrentUser is canceled`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -746,6 +974,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -1577,9 +1811,9 @@ class VaultRepositoryTest {
@Test
fun `clearUnlockedData should update the vaultDataStateFlow to Loading`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -1587,6 +1821,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -1630,9 +1870,8 @@ class VaultRepositoryTest {
@Test
fun `clearUnlockedData should update the sendDataStateFlow to Loading`() =
runTest {
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(number = 1))
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -1640,6 +1879,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(number = 1)).asSuccess()
@ -1683,9 +1928,9 @@ class VaultRepositoryTest {
val itemId = 1234
val itemIdString = "mockId-$itemId"
val item = createMockCipherView(itemId)
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(itemId))
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = itemId)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -1693,6 +1938,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(itemId)))
} returns listOf(item).asSuccess()
@ -1728,6 +1979,7 @@ class VaultRepositoryTest {
val folderId = 1234
val folderIdString = "mockId-$folderId"
val throwable = Throwable("Fail")
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery {
syncService.sync()
} returns throwable.asFailure()
@ -1748,6 +2000,7 @@ class VaultRepositoryTest {
runTest {
val itemId = 1234
val itemIdString = "mockId-$itemId"
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery {
syncService.sync()
} returns UnknownHostException().asFailure()
@ -1766,10 +2019,10 @@ class VaultRepositoryTest {
@Test
fun `getVaultItemStateFlow should update to Loaded with null when a item cannot be found`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val itemIdString = "mockId-1234"
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(1))
val mockSyncResponse = createMockSyncResponse(1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -1777,6 +2030,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(1)).asSuccess()
@ -1806,12 +2065,12 @@ class VaultRepositoryTest {
@Test
fun `getVaultFolderStateFlow should receive updates whenever a sync is called`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val folderId = 1234
val folderIdString = "mockId-$folderId"
val folder = createMockFolderView(folderId)
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(folderId))
val mockSyncResponse = createMockSyncResponse(folderId)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -1819,6 +2078,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(folderId)))
} returns listOf(createMockCipherView(folderId)).asSuccess()
@ -1854,6 +2119,7 @@ class VaultRepositoryTest {
runTest {
val folderId = 1234
val folderIdString = "mockId-$folderId"
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery {
syncService.sync()
} returns UnknownHostException().asFailure()
@ -1874,6 +2140,7 @@ class VaultRepositoryTest {
val folderId = 1234
val folderIdString = "mockId-$folderId"
val throwable = Throwable("Fail")
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery {
syncService.sync()
} returns throwable.asFailure()
@ -1892,10 +2159,10 @@ class VaultRepositoryTest {
@Test
fun `getVaultFolderStateFlow should update to Loaded with null when a item cannot be found`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val folderIdString = "mockId-1234"
coEvery {
syncService.sync()
} returns Result.success(createMockSyncResponse(1))
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
@ -1903,6 +2170,12 @@ class VaultRepositoryTest {
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
} returns listOf(createMockCipherView(1)).asSuccess()