mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
Add sends database table (#490)
This commit is contained in:
parent
cd707473fc
commit
b6032873ec
10 changed files with 377 additions and 228 deletions
|
@ -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`.
|
||||
*
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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> =
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue