Add sends database table (#490)

This commit is contained in:
David Perez 2024-01-04 10:20:54 -06:00 committed by Álison Fernandes
parent cd707473fc
commit b6032873ec
10 changed files with 377 additions and 228 deletions

View file

@ -23,6 +23,11 @@ interface VaultDiskSource {
*/
fun getFolders(userId: String): Flow<List<SyncResponseJson.Folder>>
/**
* Retrieves all sends from the data source for a given [userId].
*/
fun getSends(userId: String): Flow<List<SyncResponseJson.Send>>
/**
* Replaces all [vault] data for a given [userId] with the new `vault`.
*

View file

@ -4,9 +4,11 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -24,6 +26,7 @@ class VaultDiskSourceImpl(
private val ciphersDao: CiphersDao,
private val collectionsDao: CollectionsDao,
private val foldersDao: FoldersDao,
private val sendsDao: SendsDao,
private val json: Json,
) : VaultDiskSource {
@ -31,6 +34,7 @@ class VaultDiskSourceImpl(
private val forceCollectionsFlow =
bufferedMutableSharedFlow<List<SyncResponseJson.Collection>>()
private val forceFolderFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
private val forceSendFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
override fun getCiphers(
userId: String,
@ -85,6 +89,21 @@ class VaultDiskSourceImpl(
},
)
override fun getSends(
userId: String,
): Flow<List<SyncResponseJson.Send>> =
merge(
forceSendFlow,
sendsDao
.getAllSends(userId = userId)
.map { entities ->
entities.map { entity ->
json.decodeFromString<SyncResponseJson.Send>(entity.sendJson)
}
},
)
@Suppress("LongMethod")
override suspend fun replaceVaultData(
userId: String,
vault: SyncResponseJson,
@ -132,6 +151,19 @@ class VaultDiskSourceImpl(
},
)
}
val deferredSends = async {
sendsDao.replaceAllSends(
userId = userId,
sends = vault.sends.orEmpty().map { send ->
SendEntity(
userId = userId,
id = send.id,
sendType = json.encodeToString(send.type),
sendJson = json.encodeToString(send),
)
},
)
}
// When going from 0 items to 0 items, the respective dao flow will not re-emit
// So we use this to give it a little push.
if (!deferredCiphers.await()) {
@ -143,6 +175,9 @@ class VaultDiskSourceImpl(
if (!deferredFolders.await()) {
forceFolderFlow.tryEmit(emptyList())
}
if (!deferredSends.await()) {
forceSendFlow.tryEmit(emptyList())
}
}
}
@ -151,10 +186,12 @@ class VaultDiskSourceImpl(
val deferredCiphers = async { ciphersDao.deleteAllCiphers(userId = userId) }
val deferredCollections = async { collectionsDao.deleteAllCollections(userId = userId) }
val deferredFolders = async { foldersDao.deleteAllFolders(userId = userId) }
val deferredSends = async { sendsDao.deleteAllSends(userId = userId) }
awaitAll(
deferredCiphers,
deferredCollections,
deferredFolders,
deferredSends,
)
}
}

View file

@ -0,0 +1,50 @@
package com.x8bit.bitwarden.data.vault.datasource.disk.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
import kotlinx.coroutines.flow.Flow
/**
* Provides methods for inserting, retrieving, and deleting sends from the database using the
* [SendEntity].
*/
@Dao
interface SendsDao {
/**
* Inserts multiple sends into the database.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSends(sends: List<SendEntity>)
/**
* Retrieves all sends from the database for a given [userId].
*/
@Query("SELECT * FROM sends WHERE user_id = :userId")
fun getAllSends(
userId: String,
): Flow<List<SendEntity>>
/**
* Deletes all the stored sends associated with the given [userId]. This will return the
* number of rows deleted by this query.
*/
@Query("DELETE FROM sends WHERE user_id = :userId")
suspend fun deleteAllSends(userId: String): Int
/**
* Deletes all the stored sends associated with the given [userId] and then add all new
* [sends] to the database. This will return `true` if any changes were made to the database
* and `false` otherwise.
*/
@Transaction
suspend fun replaceAllSends(userId: String, sends: List<SendEntity>): Boolean {
val deletedSendsCount = deleteAllSends(userId)
insertSends(sends)
return deletedSendsCount > 0 || sends.isNotEmpty()
}
}

View file

@ -7,9 +7,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTyp
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
/**
* Room database for storing any persisted data from the vault sync.
@ -19,8 +21,9 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
CipherEntity::class,
CollectionEntity::class,
FolderEntity::class,
SendEntity::class,
],
version = 1,
version = 2,
)
@TypeConverters(ZonedDateTimeTypeConverter::class)
abstract class VaultDatabase : RoomDatabase() {
@ -39,4 +42,9 @@ abstract class VaultDatabase : RoomDatabase() {
* Provides the DAO for accessing folder data.
*/
abstract fun folderDao(): FoldersDao
/**
* Provides the DAO for accessing send data.
*/
abstract fun sendsDao(): SendsDao
}

View file

@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTyp
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.SendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.database.VaultDatabase
import dagger.Module
import dagger.Provides
@ -32,6 +33,7 @@ class VaultDiskModule {
klass = VaultDatabase::class.java,
name = "vault_database",
)
.fallbackToDestructiveMigration()
.addTypeConverter(ZonedDateTimeTypeConverter())
.build()
@ -47,17 +49,23 @@ class VaultDiskModule {
@Singleton
fun provideFolderDao(database: VaultDatabase): FoldersDao = database.folderDao()
@Provides
@Singleton
fun provideSendDao(database: VaultDatabase): SendsDao = database.sendsDao()
@Provides
@Singleton
fun provideVaultDiskSource(
ciphersDao: CiphersDao,
collectionsDao: CollectionsDao,
foldersDao: FoldersDao,
sendsDao: SendsDao,
json: Json,
): VaultDiskSource = VaultDiskSourceImpl(
ciphersDao = ciphersDao,
collectionsDao = collectionsDao,
foldersDao = foldersDao,
sendsDao = sendsDao,
json = json,
)
}

View file

@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.vault.datasource.disk.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* Entity representing a send in the database.
*/
@Entity(tableName = "sends")
data class SendEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "user_id", index = true)
val userId: String,
@ColumnInfo(name = "send_type")
val sendType: String,
@ColumnInfo(name = "send_json")
val sendJson: String,
)

View file

@ -164,6 +164,12 @@ class VaultRepositoryImpl(
observeVaultDiskCollections(activeUserId)
}
.launchIn(unconfinedScope)
// Setup sends MutableStateFlow
mutableSendDataStateFlow
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
observeVaultDiskSends(activeUserId)
}
.launchIn(unconfinedScope)
}
override fun clearUnlockedData() {
@ -201,7 +207,6 @@ class VaultRepositoryImpl(
unlockVaultForOrganizationsIfNecessary(syncResponse = syncResponse)
storeProfileData(syncResponse = syncResponse)
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
decryptSendsAndUpdateSendDataState(sendList = syncResponse.sends)
},
onFailure = { throwable ->
mutableCiphersStateFlow.update { currentState ->
@ -474,20 +479,6 @@ class VaultRepositoryImpl(
)
}
private suspend fun decryptSendsAndUpdateSendDataState(sendList: List<SyncResponseJson.Send>?) {
val newState = vaultSdkSource
.decryptSendList(
sendList = sendList
.orEmpty()
.toEncryptedSdkSendList(),
)
.fold(
onSuccess = { DataState.Loaded(data = SendData(sendViewList = it)) },
onFailure = { DataState.Error(error = it) },
)
mutableSendDataStateFlow.update { newState }
}
private fun observeVaultDiskCiphers(
userId: String,
): Flow<DataState<List<CipherView>>> =
@ -535,6 +526,22 @@ class VaultRepositoryImpl(
)
}
.onEach { mutableCollectionsStateFlow.value = it }
private fun observeVaultDiskSends(
userId: String,
): Flow<DataState<SendData>> =
vaultDiskSource
.getSends(userId = userId)
.onStart { mutableSendDataStateFlow.value = DataState.Loading }
.map {
vaultSdkSource
.decryptSendList(sendList = it.toEncryptedSdkSendList())
.fold(
onSuccess = { sends -> DataState.Loaded(SendData(sends)) },
onFailure = { throwable -> DataState.Error(throwable) },
)
}
.onEach { mutableSendDataStateFlow.value = it }
}
private fun <T> Throwable.toNetworkOrErrorState(data: T?): DataState<T> =

View file

@ -6,13 +6,16 @@ import com.x8bit.bitwarden.data.util.assertJsonEquals
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FakeCiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FakeCollectionsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FakeFoldersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FakeSendsDao
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
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.createMockCollection
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
@ -29,6 +32,7 @@ class VaultDiskSourceTest {
private lateinit var ciphersDao: FakeCiphersDao
private lateinit var collectionsDao: FakeCollectionsDao
private lateinit var foldersDao: FakeFoldersDao
private lateinit var sendsDao: FakeSendsDao
private lateinit var vaultDiskSource: VaultDiskSource
@ -37,10 +41,12 @@ class VaultDiskSourceTest {
ciphersDao = FakeCiphersDao()
collectionsDao = FakeCollectionsDao()
foldersDao = FakeFoldersDao()
sendsDao = FakeSendsDao()
vaultDiskSource = VaultDiskSourceImpl(
ciphersDao = ciphersDao,
collectionsDao = collectionsDao,
foldersDao = foldersDao,
sendsDao = sendsDao,
json = json,
)
}
@ -87,17 +93,30 @@ class VaultDiskSourceTest {
}
}
@Test
fun `getSends should emit all SendsDao updates`() = runTest {
val sendEntities = listOf(SEND_ENTITY)
val sends = listOf(SEND_1)
vaultDiskSource
.getSends(USER_ID)
.test {
assertEquals(emptyList<SyncResponseJson.Send>(), awaitItem())
sendsDao.insertSends(sendEntities)
assertEquals(sends, awaitItem())
}
}
@Test
fun `replaceVaultData should clear the daos and insert the new vault data`() = runTest {
assertEquals(ciphersDao.storedCiphers, emptyList<CipherEntity>())
assertEquals(collectionsDao.storedCollections, emptyList<CollectionEntity>())
assertEquals(foldersDao.storedFolders, emptyList<FolderEntity>())
assertEquals(sendsDao.storedSends, emptyList<SendEntity>())
vaultDiskSource.replaceVaultData(USER_ID, VAULT_DATA)
assertEquals(1, ciphersDao.storedCiphers.size)
assertEquals(1, foldersDao.storedFolders.size)
// Verify the ciphers dao is updated
val storedCipherEntity = ciphersDao.storedCiphers.first()
// We cannot compare the JSON strings directly because of formatting differences
@ -110,6 +129,14 @@ class VaultDiskSourceTest {
// Verify the folders dao is updated
assertEquals(listOf(FOLDER_ENTITY), foldersDao.storedFolders)
assertEquals(1, sendsDao.storedSends.size)
// Verify the ciphers dao is updated
val storedSendEntity = sendsDao.storedSends.first()
// We cannot compare the JSON strings directly because of formatting differences
// So we split that off into its own assertion.
assertEquals(SEND_ENTITY.copy(sendJson = ""), storedSendEntity.copy(sendJson = ""))
assertJsonEquals(SEND_ENTITY.sendJson, storedSendEntity.sendJson)
}
@Test
@ -117,10 +144,12 @@ class VaultDiskSourceTest {
assertFalse(ciphersDao.deleteCiphersCalled)
assertFalse(collectionsDao.deleteCollectionsCalled)
assertFalse(foldersDao.deleteFoldersCalled)
assertFalse(sendsDao.deleteSendsCalled)
vaultDiskSource.deleteVaultData(USER_ID)
assertTrue(ciphersDao.deleteCiphersCalled)
assertTrue(collectionsDao.deleteCollectionsCalled)
assertTrue(foldersDao.deleteFoldersCalled)
assertTrue(sendsDao.deleteSendsCalled)
}
}
@ -129,6 +158,7 @@ private const val USER_ID: String = "test_user_id"
private val CIPHER_1: SyncResponseJson.Cipher = createMockCipher(1)
private val COLLECTION_1: SyncResponseJson.Collection = createMockCollection(3)
private val FOLDER_1: SyncResponseJson.Folder = createMockFolder(2)
private val SEND_1: SyncResponseJson.Send = createMockSend(1)
private val VAULT_DATA: SyncResponseJson = SyncResponseJson(
folders = listOf(FOLDER_1),
@ -142,7 +172,7 @@ private val VAULT_DATA: SyncResponseJson = SyncResponseJson(
globalEquivalentDomains = null,
equivalentDomains = null,
),
sends = null,
sends = listOf(SEND_1),
)
private const val CIPHER_JSON = """
@ -260,3 +290,39 @@ private val FOLDER_ENTITY = FolderEntity(
name = "mockName-2",
revisionDate = ZonedDateTime.parse("2023-10-27T12:00Z"),
)
private const val SEND_JSON = """
{
"accessCount": 1,
"notes": "mockNotes-1",
"revisionDate": "2023-10-27T12:00:00.000Z",
"maxAccessCount": 1,
"hideEmail": false,
"type": 1,
"accessId": "mockAccessId-1",
"password": "mockPassword-1",
"file": {
"fileName": "mockFileName-1",
"size": 1,
"sizeName": "mockSizeName-1",
"id": "mockId-1"
},
"deletionDate": "2023-10-27T12:00:00.000Z",
"name": "mockName-1",
"disabled": false,
"id": "mockId-1",
"text": {
"hidden": false,
"text": "mockText-1"
},
"key": "mockKey-1",
"expirationDate": "2023-10-27T12:00:00.000Z"
}
"""
private val SEND_ENTITY = SendEntity(
id = "mockId-1",
userId = USER_ID,
sendType = "1",
sendJson = SEND_JSON,
)

View file

@ -0,0 +1,42 @@
package com.x8bit.bitwarden.data.vault.datasource.disk.dao
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class FakeSendsDao : SendsDao {
val storedSends = mutableListOf<SendEntity>()
var deleteSendsCalled: Boolean = false
private val sendsFlow = bufferedMutableSharedFlow<List<SendEntity>>(replay = 1)
init {
sendsFlow.tryEmit(emptyList())
}
override suspend fun deleteAllSends(userId: String): Int {
deleteSendsCalled = true
val count = storedSends.count { it.userId == userId }
storedSends.removeAll { it.userId == userId }
sendsFlow.tryEmit(storedSends.toList())
return count
}
override fun getAllSends(userId: String): Flow<List<SendEntity>> =
sendsFlow.map { ciphers -> ciphers.filter { it.userId == userId } }
override suspend fun insertSends(sends: List<SendEntity>) {
storedSends.addAll(sends)
sendsFlow.tryEmit(storedSends.toList())
}
override suspend fun replaceAllSends(userId: String, sends: List<SendEntity>): Boolean {
val removed = storedSends.removeAll { it.userId == userId }
storedSends.addAll(sends)
sendsFlow.tryEmit(storedSends.toList())
return removed || sends.isNotEmpty()
}
}

View file

@ -28,6 +28,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCollect
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganizationKeys
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSyncResponse
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
@ -50,6 +51,7 @@ 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 com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
import io.mockk.awaits
import io.mockk.coEvery
import io.mockk.coVerify
@ -234,6 +236,55 @@ class VaultRepositoryTest {
}
}
@Test
fun `sendDataStateFlow should emit decrypted list of sends when decryptSendsList succeeds`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSendList = listOf(createMockSend(number = 1))
val mockEncryptedSendList = mockSendList.toEncryptedSdkSendList()
val mockSendViewList = listOf(createMockSendView(number = 1))
val mutableSendsStateFlow =
bufferedMutableSharedFlow<List<SyncResponseJson.Send>>(replay = 1)
every {
vaultDiskSource.getSends(userId = MOCK_USER_STATE.activeUserId)
} returns mutableSendsStateFlow
coEvery {
vaultSdkSource.decryptSendList(mockEncryptedSendList)
} returns mockSendViewList.asSuccess()
vaultRepository
.sendDataStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableSendsStateFlow.tryEmit(mockSendList)
assertEquals(DataState.Loaded(SendData(mockSendViewList)), awaitItem())
}
}
@Test
fun `sendDataStateFlow should emit an error when decryptSendsList fails`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val throwable = Throwable("Fail")
val mockSendList = listOf(createMockSend(number = 1))
val mockEncryptedSendList = mockSendList.toEncryptedSdkSendList()
val mutableSendsStateFlow =
bufferedMutableSharedFlow<List<SyncResponseJson.Send>>(replay = 1)
every {
vaultDiskSource.getSends(userId = MOCK_USER_STATE.activeUserId)
} returns mutableSendsStateFlow
coEvery {
vaultSdkSource.decryptSendList(mockEncryptedSendList)
} returns throwable.asFailure()
vaultRepository
.sendDataStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
mutableSendsStateFlow.tryEmit(mockSendList)
assertEquals(DataState.Error<SendData>(throwable), awaitItem())
}
}
@Test
fun `deleteVaultData should call deleteVaultData on VaultDiskSource`() {
val userId = "userId-1234"
@ -248,7 +299,7 @@ class VaultRepositoryTest {
@Suppress("MaxLineLength")
@Test
fun `sync with syncService Success should unlock the vault for orgs if necessary and update AuthDiskSource and sendDataStateFlows`() =
fun `sync with syncService Success should unlock the vault for orgs if necessary and update AuthDiskSource and VaultDiskSource`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
@ -266,10 +317,6 @@ class VaultRepositoryTest {
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
vaultRepository.sync()
@ -303,14 +350,6 @@ class VaultRepositoryTest {
userId = "mockId-1",
organizations = listOf(createMockOrganization(number = 1)),
)
assertEquals(
DataState.Loaded(
data = SendData(
sendViewList = listOf(createMockSendView(number = 1)),
),
),
vaultRepository.sendDataStateFlow.value,
)
coVerify {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
@ -325,121 +364,30 @@ class VaultRepositoryTest {
}
@Test
fun `sync with data should update sendDataStateFlow to Pending before service sync`() =
runTest {
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
organizationKeys = createMockOrganizationKeys(1),
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
fun `sync with syncService Failure should update DataStateFlow with an Error`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockException = IllegalStateException("sad")
coEvery { syncService.sync() } returns mockException.asFailure()
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(),
)
}
}
vaultRepository.sync()
@Test
fun `sync with decryptSendList Failure should update sendDataStateFlows with Error`() =
runTest {
val mockException = IllegalStateException()
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
organizationKeys = createMockOrganizationKeys(1),
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns mockException.asFailure()
fakeAuthDiskSource.userState = MOCK_USER_STATE
vaultRepository.sync()
assertEquals(
DataState.Error<SendData>(error = mockException),
vaultRepository.sendDataStateFlow.value,
)
}
@Test
fun `sync with syncService Failure should update DataStateFlow with an Error`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockException = IllegalStateException("sad")
coEvery { syncService.sync() } returns mockException.asFailure()
vaultRepository.sync()
assertEquals(
DataState.Error<List<CipherView>>(mockException),
vaultRepository.ciphersStateFlow.value,
)
assertEquals(
DataState.Error<List<CollectionView>>(mockException),
vaultRepository.collectionsStateFlow.value,
)
assertEquals(
DataState.Error<List<FolderView>>(mockException),
vaultRepository.foldersStateFlow.value,
)
assertEquals(
DataState.Error<SendData>(mockException),
vaultRepository.sendDataStateFlow.value,
)
}
assertEquals(
DataState.Error<List<CipherView>>(mockException),
vaultRepository.ciphersStateFlow.value,
)
assertEquals(
DataState.Error<List<CollectionView>>(mockException),
vaultRepository.collectionsStateFlow.value,
)
assertEquals(
DataState.Error<List<FolderView>>(mockException),
vaultRepository.foldersStateFlow.value,
)
assertEquals(
DataState.Error<SendData>(mockException),
vaultRepository.sendDataStateFlow.value,
)
}
@Test
fun `sync with syncService Failure should update vaultDataStateFlow with an Error`() = runTest {
@ -497,66 +445,37 @@ class VaultRepositoryTest {
}
}
@Suppress("MaxLineLength")
@Test
fun `sync with NoNetwork data should update sendDataStateFlow to NoNetwork with data`() =
fun `sync with NoNetwork data should update sendDataStateFlow to Pending and NoNetwork with data`() =
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns UnknownHostException().asFailure()
val sendsFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
setupVaultDiskSourceFlows(sendsFlow = sendsFlow)
coEvery {
syncService.sync()
} returnsMany listOf(
mockSyncResponse.asSuccess(),
UnknownHostException().asFailure(),
)
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
organizationKeys = createMockOrganizationKeys(1),
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(1)))
} returns listOf(createMockSendView(1)).asSuccess()
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(),
)
}
vaultRepository
.sendDataStateFlow
.test {
assertEquals(DataState.Loading, awaitItem())
sendsFlow.tryEmit(listOf(createMockSend(1)))
assertEquals(
DataState.Loaded(data = SendData(listOf(createMockSendView(1)))),
awaitItem(),
)
vaultRepository.sync()
assertEquals(
DataState.Pending(data = SendData(listOf(createMockSendView(1)))),
awaitItem(),
)
assertEquals(
DataState.NoNetwork(data = SendData(listOf(createMockSendView(1)))),
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@ -1536,47 +1455,28 @@ class VaultRepositoryTest {
@Test
fun `clearUnlockedData should update the sendDataStateFlow to Loading`() = runTest {
val mockSyncResponse = createMockSyncResponse(number = 1)
coEvery { syncService.sync() } returns mockSyncResponse.asSuccess()
coEvery {
vaultSdkSource.initializeOrganizationCrypto(
request = InitOrgCryptoRequest(
organizationKeys = createMockOrganizationKeys(1),
),
)
} returns InitializeCryptoResult.Success.asSuccess()
coEvery {
vaultDiskSource.replaceVaultData(
userId = MOCK_USER_STATE.activeUserId,
vault = mockSyncResponse,
)
} just runs
fakeAuthDiskSource.userState = MOCK_USER_STATE
coEvery {
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(number = 1)))
} returns listOf(createMockSendView(number = 1)).asSuccess()
fakeAuthDiskSource.userState = MOCK_USER_STATE
val sendsFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
setupVaultDiskSourceFlows(sendsFlow = sendsFlow)
vaultRepository.sendDataStateFlow.test {
assertEquals(
DataState.Loading,
awaitItem(),
)
vaultRepository.sync()
assertEquals(DataState.Loading, awaitItem())
sendsFlow.tryEmit(listOf(createMockSend(number = 1)))
assertEquals(
DataState.Loaded(
data = SendData(
sendViewList = listOf(createMockSendView(number = 1)),
),
data = SendData(sendViewList = listOf(createMockSendView(number = 1))),
),
awaitItem(),
)
vaultRepository.clearUnlockedData()
assertEquals(
DataState.Loading,
awaitItem(),
)
assertEquals(DataState.Loading, awaitItem())
}
}
@ -1857,12 +1757,14 @@ class VaultRepositoryTest {
ciphersFlow: Flow<List<SyncResponseJson.Cipher>> = bufferedMutableSharedFlow(),
collectionsFlow: Flow<List<SyncResponseJson.Collection>> = bufferedMutableSharedFlow(),
foldersFlow: Flow<List<SyncResponseJson.Folder>> = bufferedMutableSharedFlow(),
sendsFlow: Flow<List<SyncResponseJson.Send>> = bufferedMutableSharedFlow(),
) {
coEvery { vaultDiskSource.getCiphers(MOCK_USER_STATE.activeUserId) } returns ciphersFlow
coEvery {
vaultDiskSource.getCollections(MOCK_USER_STATE.activeUserId)
} returns collectionsFlow
coEvery { vaultDiskSource.getFolders(MOCK_USER_STATE.activeUserId) } returns foldersFlow
coEvery { vaultDiskSource.getSends(MOCK_USER_STATE.activeUserId) } returns sendsFlow
}
/**