mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Add Domains database (#784)
This commit is contained in:
parent
5fa49c8b53
commit
52acc2fa47
15 changed files with 416 additions and 5 deletions
|
@ -34,6 +34,11 @@ interface VaultDiskSource {
|
|||
*/
|
||||
fun getCollections(userId: String): Flow<List<SyncResponseJson.Collection>>
|
||||
|
||||
/**
|
||||
* Retrieves all domains from the data source for a given [userId].
|
||||
*/
|
||||
fun getDomains(userId: String): Flow<SyncResponseJson.Domains>
|
||||
|
||||
/**
|
||||
* Saves a folder to the data source for the given [userId].
|
||||
*/
|
||||
|
|
|
@ -3,10 +3,12 @@ package com.x8bit.bitwarden.data.vault.datasource.disk
|
|||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
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.DomainsDao
|
||||
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.DomainsEntity
|
||||
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
|
||||
|
@ -16,6 +18,7 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
|
@ -26,6 +29,7 @@ import kotlinx.serialization.json.Json
|
|||
class VaultDiskSourceImpl(
|
||||
private val ciphersDao: CiphersDao,
|
||||
private val collectionsDao: CollectionsDao,
|
||||
private val domainsDao: DomainsDao,
|
||||
private val foldersDao: FoldersDao,
|
||||
private val sendsDao: SendsDao,
|
||||
private val json: Json,
|
||||
|
@ -103,6 +107,13 @@ class VaultDiskSourceImpl(
|
|||
},
|
||||
)
|
||||
|
||||
override fun getDomains(userId: String): Flow<SyncResponseJson.Domains> =
|
||||
domainsDao
|
||||
.getDomains(userId)
|
||||
.map { entity ->
|
||||
json.decodeFromString<SyncResponseJson.Domains>(entity.domainsJson)
|
||||
}
|
||||
|
||||
override suspend fun saveFolder(userId: String, folder: SyncResponseJson.Folder) {
|
||||
foldersDao.insertFolder(
|
||||
folder = FolderEntity(
|
||||
|
@ -198,6 +209,14 @@ class VaultDiskSourceImpl(
|
|||
},
|
||||
)
|
||||
}
|
||||
launch {
|
||||
domainsDao.insertDomains(
|
||||
domains = DomainsEntity(
|
||||
userId = userId,
|
||||
domainsJson = json.encodeToString(vault.domains),
|
||||
),
|
||||
)
|
||||
}
|
||||
val deferredFolders = async {
|
||||
foldersDao.replaceAllFolders(
|
||||
userId = userId,
|
||||
|
@ -245,11 +264,13 @@ class VaultDiskSourceImpl(
|
|||
coroutineScope {
|
||||
val deferredCiphers = async { ciphersDao.deleteAllCiphers(userId = userId) }
|
||||
val deferredCollections = async { collectionsDao.deleteAllCollections(userId = userId) }
|
||||
val deferredDomains = async { domainsDao.deleteDomains(userId = userId) }
|
||||
val deferredFolders = async { foldersDao.deleteAllFolders(userId = userId) }
|
||||
val deferredSends = async { sendsDao.deleteAllSends(userId = userId) }
|
||||
awaitAll(
|
||||
deferredCiphers,
|
||||
deferredCollections,
|
||||
deferredDomains,
|
||||
deferredFolders,
|
||||
deferredSends,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
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 com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Provides methods for inserting, retrieving, and deleting domains from the database using the
|
||||
* [DomainsEntity].
|
||||
*/
|
||||
@Dao
|
||||
interface DomainsDao {
|
||||
/**
|
||||
* Deletes the stored domains associated with the given [userId].
|
||||
*/
|
||||
@Query("DELETE FROM domains WHERE user_id = :userId")
|
||||
suspend fun deleteDomains(userId: String)
|
||||
|
||||
/**
|
||||
* Retrieves domains from the database for a given [userId].
|
||||
*/
|
||||
@Query("SELECT * FROM domains WHERE user_id = :userId")
|
||||
fun getDomains(
|
||||
userId: String,
|
||||
): Flow<DomainsEntity>
|
||||
|
||||
/**
|
||||
* Inserts domains into the database.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertDomains(domains: DomainsEntity)
|
||||
}
|
|
@ -6,10 +6,12 @@ import androidx.room.TypeConverters
|
|||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTypeConverter
|
||||
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.DomainsDao
|
||||
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.DomainsEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||
|
||||
|
@ -20,10 +22,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
|||
entities = [
|
||||
CipherEntity::class,
|
||||
CollectionEntity::class,
|
||||
DomainsEntity::class,
|
||||
FolderEntity::class,
|
||||
SendEntity::class,
|
||||
],
|
||||
version = 2,
|
||||
version = 3,
|
||||
)
|
||||
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
||||
abstract class VaultDatabase : RoomDatabase() {
|
||||
|
@ -38,6 +41,11 @@ abstract class VaultDatabase : RoomDatabase() {
|
|||
*/
|
||||
abstract fun collectionDao(): CollectionsDao
|
||||
|
||||
/**
|
||||
* Provides the DAO for accessing domains data.
|
||||
*/
|
||||
abstract fun domainsDao(): DomainsDao
|
||||
|
||||
/**
|
||||
* Provides the DAO for accessing folder data.
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSourceImpl
|
|||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTypeConverter
|
||||
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.DomainsDao
|
||||
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
|
||||
|
@ -45,6 +46,10 @@ class VaultDiskModule {
|
|||
@Singleton
|
||||
fun provideCollectionDao(database: VaultDatabase): CollectionsDao = database.collectionDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDomainsDao(database: VaultDatabase): DomainsDao = database.domainsDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFolderDao(database: VaultDatabase): FoldersDao = database.folderDao()
|
||||
|
@ -58,12 +63,14 @@ class VaultDiskModule {
|
|||
fun provideVaultDiskSource(
|
||||
ciphersDao: CiphersDao,
|
||||
collectionsDao: CollectionsDao,
|
||||
domainsDao: DomainsDao,
|
||||
foldersDao: FoldersDao,
|
||||
sendsDao: SendsDao,
|
||||
json: Json,
|
||||
): VaultDiskSource = VaultDiskSourceImpl(
|
||||
ciphersDao = ciphersDao,
|
||||
collectionsDao = collectionsDao,
|
||||
domainsDao = domainsDao,
|
||||
foldersDao = foldersDao,
|
||||
sendsDao = sendsDao,
|
||||
json = json,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.disk.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* Entity representing a set of domains in the database.
|
||||
*/
|
||||
@Entity(tableName = "domains")
|
||||
data class DomainsEntity(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
@ColumnInfo(name = "user_id")
|
||||
val userId: String,
|
||||
|
||||
@ColumnInfo(name = "domains_json")
|
||||
val domainsJson: String,
|
||||
)
|
|
@ -17,6 +17,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
|
@ -69,6 +70,14 @@ interface VaultRepository : VaultLockManager {
|
|||
*/
|
||||
val collectionsStateFlow: StateFlow<DataState<List<CollectionView>>>
|
||||
|
||||
/**
|
||||
* Flow that represents all domains for the active user.
|
||||
*
|
||||
* Note that the [StateFlow.value] will return the last known value but the [StateFlow] itself
|
||||
* must be collected in order to trigger state changes.
|
||||
*/
|
||||
val domainsStateFlow: StateFlow<DataState<DomainsData>>
|
||||
|
||||
/**
|
||||
* Flow that represents all folders for the active user.
|
||||
*
|
||||
|
|
|
@ -46,6 +46,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
|
@ -56,6 +57,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherResponse
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend
|
||||
|
@ -136,6 +138,9 @@ class VaultRepositoryImpl(
|
|||
private val mutableCollectionsStateFlow =
|
||||
MutableStateFlow<DataState<List<CollectionView>>>(DataState.Loading)
|
||||
|
||||
private val mutableDomainsStateFlow =
|
||||
MutableStateFlow<DataState<DomainsData>>(DataState.Loading)
|
||||
|
||||
override var vaultFilterType: VaultFilterType = VaultFilterType.AllVaults
|
||||
|
||||
override val vaultDataStateFlow: StateFlow<DataState<VaultData>> =
|
||||
|
@ -171,6 +176,9 @@ class VaultRepositoryImpl(
|
|||
override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
|
||||
get() = mutableCiphersStateFlow.asStateFlow()
|
||||
|
||||
override val domainsStateFlow: StateFlow<DataState<DomainsData>>
|
||||
get() = mutableDomainsStateFlow.asStateFlow()
|
||||
|
||||
override val foldersStateFlow: StateFlow<DataState<List<FolderView>>>
|
||||
get() = mutableFoldersStateFlow.asStateFlow()
|
||||
|
||||
|
@ -187,6 +195,12 @@ class VaultRepositoryImpl(
|
|||
observeVaultDiskCiphers(activeUserId)
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
// Setup domains MutableStateFlow
|
||||
mutableDomainsStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
observeVaultDiskDomains(activeUserId)
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
// Setup folders MutableStateFlow
|
||||
mutableFoldersStateFlow
|
||||
.observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId ->
|
||||
|
@ -209,6 +223,7 @@ class VaultRepositoryImpl(
|
|||
|
||||
override fun clearUnlockedData() {
|
||||
mutableCiphersStateFlow.update { DataState.Loading }
|
||||
mutableDomainsStateFlow.update { DataState.Loading }
|
||||
mutableFoldersStateFlow.update { DataState.Loading }
|
||||
mutableCollectionsStateFlow.update { DataState.Loading }
|
||||
mutableSendDataStateFlow.update { DataState.Loading }
|
||||
|
@ -224,6 +239,7 @@ class VaultRepositoryImpl(
|
|||
val userId = activeUserId ?: return
|
||||
if (!syncJob.isCompleted || isVaultUnlocking(userId)) return
|
||||
mutableCiphersStateFlow.updateToPendingOrLoading()
|
||||
mutableDomainsStateFlow.updateToPendingOrLoading()
|
||||
mutableFoldersStateFlow.updateToPendingOrLoading()
|
||||
mutableCollectionsStateFlow.updateToPendingOrLoading()
|
||||
mutableSendDataStateFlow.updateToPendingOrLoading()
|
||||
|
@ -250,6 +266,11 @@ class VaultRepositoryImpl(
|
|||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
mutableDomainsStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
)
|
||||
}
|
||||
mutableFoldersStateFlow.update { currentState ->
|
||||
throwable.toNetworkOrErrorState(
|
||||
data = currentState.data,
|
||||
|
@ -993,6 +1014,19 @@ class VaultRepositoryImpl(
|
|||
}
|
||||
.onEach { mutableCiphersStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskDomains(
|
||||
userId: String,
|
||||
): Flow<DataState<DomainsData>> =
|
||||
vaultDiskSource
|
||||
.getDomains(userId = userId)
|
||||
.onStart { mutableDomainsStateFlow.value = DataState.Loading }
|
||||
.map {
|
||||
DataState.Loaded(
|
||||
data = it.toDomainsData(),
|
||||
)
|
||||
}
|
||||
.onEach { mutableDomainsStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskFolders(
|
||||
userId: String,
|
||||
): Flow<DataState<List<FolderView>>> =
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Model for equivalent domain details.
|
||||
*
|
||||
* @param equivalentDomains A list of equivalent domains to compare URIs to.
|
||||
* @param globalEquivalentDomains A list of global equivalent domains to compare URIs to.
|
||||
*/
|
||||
data class DomainsData(
|
||||
val equivalentDomains: List<List<String>>,
|
||||
val globalEquivalentDomains: List<GlobalEquivalentDomain>,
|
||||
) {
|
||||
/**
|
||||
* Model for a group of domains that should be matched together.
|
||||
*
|
||||
* @property isExcluded If the global equivalent domain should be excluded.
|
||||
* @property domains A list of domains that should all match a URI.
|
||||
* @property type The domain type identifier.
|
||||
*/
|
||||
data class GlobalEquivalentDomain(
|
||||
val isExcluded: Boolean,
|
||||
val domains: List<String>,
|
||||
val type: Int,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson.Domains
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
|
||||
/**
|
||||
* Map the API [Domains] model to the internal [DomainsData] model.
|
||||
*/
|
||||
fun Domains.toDomainsData(): DomainsData {
|
||||
val globalEquivalentDomains = this
|
||||
.globalEquivalentDomains
|
||||
?.map { it.toInternalModel() }
|
||||
.orEmpty()
|
||||
|
||||
return DomainsData(
|
||||
equivalentDomains = this.equivalentDomains.orEmpty(),
|
||||
globalEquivalentDomains = globalEquivalentDomains,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the API [Domains.GlobalEquivalentDomain] model to the internal
|
||||
* [DomainsData.GlobalEquivalentDomain] model.
|
||||
*/
|
||||
private fun Domains.GlobalEquivalentDomain.toInternalModel(): DomainsData.GlobalEquivalentDomain =
|
||||
DomainsData.GlobalEquivalentDomain(
|
||||
domains = this.domains.orEmpty(),
|
||||
isExcluded = this.isExcluded,
|
||||
type = this.type,
|
||||
)
|
|
@ -5,15 +5,18 @@ import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkMo
|
|||
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.FakeDomainsDao
|
||||
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.DomainsEntity
|
||||
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.createMockDomains
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
|
||||
import io.mockk.every
|
||||
|
@ -21,6 +24,8 @@ import io.mockk.mockk
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -31,6 +36,7 @@ class VaultDiskSourceTest {
|
|||
private val json = PlatformNetworkModule.providesJson()
|
||||
private lateinit var ciphersDao: FakeCiphersDao
|
||||
private lateinit var collectionsDao: FakeCollectionsDao
|
||||
private lateinit var domainsDao: FakeDomainsDao
|
||||
private lateinit var foldersDao: FakeFoldersDao
|
||||
private lateinit var sendsDao: FakeSendsDao
|
||||
|
||||
|
@ -40,11 +46,13 @@ class VaultDiskSourceTest {
|
|||
fun setup() {
|
||||
ciphersDao = FakeCiphersDao()
|
||||
collectionsDao = FakeCollectionsDao()
|
||||
domainsDao = FakeDomainsDao()
|
||||
foldersDao = FakeFoldersDao()
|
||||
sendsDao = FakeSendsDao()
|
||||
vaultDiskSource = VaultDiskSourceImpl(
|
||||
ciphersDao = ciphersDao,
|
||||
collectionsDao = collectionsDao,
|
||||
domainsDao = domainsDao,
|
||||
foldersDao = foldersDao,
|
||||
sendsDao = sendsDao,
|
||||
json = json,
|
||||
|
@ -119,6 +127,17 @@ class VaultDiskSourceTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDomains should emit DomainsDao updates`() = runTest {
|
||||
vaultDiskSource
|
||||
.getDomains(USER_ID)
|
||||
.test {
|
||||
expectNoEvents()
|
||||
domainsDao.insertDomains(DOMAINS_ENTITY)
|
||||
assertEquals(DOMAINS_1, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveFolder should call insertFolder`() = runTest {
|
||||
assertFalse(foldersDao.insertFolderCalled)
|
||||
|
@ -191,6 +210,7 @@ class VaultDiskSourceTest {
|
|||
fun `replaceVaultData should clear the daos and insert the new vault data`() = runTest {
|
||||
assertEquals(ciphersDao.storedCiphers, emptyList<CipherEntity>())
|
||||
assertEquals(collectionsDao.storedCollections, emptyList<CollectionEntity>())
|
||||
assertNull(domainsDao.storedDomains)
|
||||
assertEquals(foldersDao.storedFolders, emptyList<FolderEntity>())
|
||||
assertEquals(sendsDao.storedSends, emptyList<SendEntity>())
|
||||
|
||||
|
@ -207,6 +227,17 @@ class VaultDiskSourceTest {
|
|||
// Verify the collections dao is updated
|
||||
assertEquals(listOf(COLLECTION_ENTITY), collectionsDao.storedCollections)
|
||||
|
||||
assertNotNull(domainsDao.storedDomains)
|
||||
// Verify the domains dao is updated
|
||||
val storedDomainsEntity = requireNotNull(domainsDao.storedDomains)
|
||||
// We cannot compare the JSON strings directly because of formatting differences
|
||||
// So we split that off into its own assertion.
|
||||
assertEquals(
|
||||
DOMAINS_ENTITY.copy(domainsJson = ""),
|
||||
storedDomainsEntity.copy(domainsJson = ""),
|
||||
)
|
||||
assertJsonEquals(DOMAINS_ENTITY.domainsJson, storedDomainsEntity.domainsJson)
|
||||
|
||||
// Verify the folders dao is updated
|
||||
assertEquals(listOf(FOLDER_ENTITY), foldersDao.storedFolders)
|
||||
|
||||
|
@ -223,11 +254,13 @@ class VaultDiskSourceTest {
|
|||
fun `deleteVaultData should remove all vault data matching the user ID`() = runTest {
|
||||
assertFalse(ciphersDao.deleteCiphersCalled)
|
||||
assertFalse(collectionsDao.deleteCollectionsCalled)
|
||||
assertFalse(domainsDao.deleteDomainsCalled)
|
||||
assertFalse(foldersDao.deleteFoldersCalled)
|
||||
assertFalse(sendsDao.deleteSendsCalled)
|
||||
vaultDiskSource.deleteVaultData(USER_ID)
|
||||
assertTrue(ciphersDao.deleteCiphersCalled)
|
||||
assertTrue(collectionsDao.deleteCollectionsCalled)
|
||||
assertTrue(domainsDao.deleteDomainsCalled)
|
||||
assertTrue(foldersDao.deleteFoldersCalled)
|
||||
assertTrue(sendsDao.deleteSendsCalled)
|
||||
}
|
||||
|
@ -237,6 +270,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 DOMAINS_1: SyncResponseJson.Domains = createMockDomains(1)
|
||||
private val FOLDER_1: SyncResponseJson.Folder = createMockFolder(2)
|
||||
private val SEND_1: SyncResponseJson.Send = createMockSend(1)
|
||||
|
||||
|
@ -248,10 +282,7 @@ private val VAULT_DATA: SyncResponseJson = SyncResponseJson(
|
|||
},
|
||||
ciphers = listOf(CIPHER_1),
|
||||
policies = null,
|
||||
domains = SyncResponseJson.Domains(
|
||||
globalEquivalentDomains = null,
|
||||
equivalentDomains = null,
|
||||
),
|
||||
domains = DOMAINS_1,
|
||||
sends = listOf(SEND_1),
|
||||
)
|
||||
|
||||
|
@ -364,6 +395,30 @@ private val COLLECTION_ENTITY = CollectionEntity(
|
|||
isReadOnly = false,
|
||||
)
|
||||
|
||||
private const val DOMAINS_JSON = """
|
||||
{
|
||||
"globalEquivalentDomains": [
|
||||
{
|
||||
"excluded": false,
|
||||
"domains": [
|
||||
"mockDomain-1"
|
||||
],
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
"equivalentDomains": [
|
||||
[
|
||||
"mockEquivalentDomain-1"
|
||||
]
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
private val DOMAINS_ENTITY = DomainsEntity(
|
||||
domainsJson = DOMAINS_JSON,
|
||||
userId = USER_ID,
|
||||
)
|
||||
|
||||
private val FOLDER_ENTITY = FolderEntity(
|
||||
id = "mockId-2",
|
||||
userId = USER_ID,
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
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.DomainsEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
|
||||
class FakeDomainsDao : DomainsDao {
|
||||
var storedDomains: DomainsEntity? = null
|
||||
|
||||
var deleteDomainsCalled: Boolean = false
|
||||
var getDomainsCalled: Boolean = false
|
||||
var insertDomainsCalled: Boolean = false
|
||||
|
||||
private val mutableDomainsFlow = bufferedMutableSharedFlow<DomainsEntity?>()
|
||||
|
||||
override suspend fun deleteDomains(userId: String) {
|
||||
deleteDomainsCalled = true
|
||||
}
|
||||
|
||||
override fun getDomains(userId: String): Flow<DomainsEntity> {
|
||||
getDomainsCalled = true
|
||||
return mutableDomainsFlow.filterNotNull()
|
||||
}
|
||||
|
||||
override suspend fun insertDomains(domains: DomainsEntity) {
|
||||
insertDomainsCalled = true
|
||||
storedDomains = domains
|
||||
mutableDomainsFlow.tryEmit(domains)
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachm
|
|||
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.createMockDomains
|
||||
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
|
||||
|
@ -70,6 +71,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
|
@ -80,6 +82,8 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
|||
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.model.createMockDomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherResponse
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||
|
@ -159,11 +163,13 @@ class VaultRepositoryTest {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(SyncResponseJson.Domains::toDomainsData)
|
||||
mockkStatic(Uri::class)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(SyncResponseJson.Domains::toDomainsData)
|
||||
unmockkStatic(Uri::class)
|
||||
unmockkStatic(Instant::class)
|
||||
unmockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
|
@ -499,6 +505,10 @@ class VaultRepositoryTest {
|
|||
DataState.Error<List<CollectionView>>(mockException),
|
||||
vaultRepository.collectionsStateFlow.value,
|
||||
)
|
||||
assertEquals(
|
||||
DataState.Error<DomainsData>(mockException),
|
||||
vaultRepository.domainsStateFlow.value,
|
||||
)
|
||||
assertEquals(
|
||||
DataState.Error<List<FolderView>>(mockException),
|
||||
vaultRepository.foldersStateFlow.value,
|
||||
|
@ -540,6 +550,10 @@ class VaultRepositoryTest {
|
|||
DataState.NoNetwork(data = null),
|
||||
vaultRepository.collectionsStateFlow.value,
|
||||
)
|
||||
assertEquals(
|
||||
DataState.NoNetwork(data = null),
|
||||
vaultRepository.domainsStateFlow.value,
|
||||
)
|
||||
assertEquals(
|
||||
DataState.NoNetwork(data = null),
|
||||
vaultRepository.foldersStateFlow.value,
|
||||
|
@ -1265,6 +1279,36 @@ class VaultRepositoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clearUnlockedData should update the domainsStateFlow to Loading`() = runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val domainsData = createMockDomainsData(number = 1)
|
||||
coEvery {
|
||||
createMockDomains(number = 1).toDomainsData()
|
||||
} returns domainsData
|
||||
val domainsFlow = bufferedMutableSharedFlow<SyncResponseJson.Domains>()
|
||||
setupVaultDiskSourceFlows(
|
||||
domainsFlow = domainsFlow,
|
||||
)
|
||||
|
||||
vaultRepository.domainsStateFlow.test {
|
||||
assertEquals(DataState.Loading, awaitItem())
|
||||
|
||||
domainsFlow.tryEmit(createMockDomains(number = 1))
|
||||
|
||||
assertEquals(
|
||||
DataState.Loaded(
|
||||
data = domainsData,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
vaultRepository.clearUnlockedData()
|
||||
|
||||
assertEquals(DataState.Loading, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultItemStateFlow should update to Error when a sync fails generically`() =
|
||||
runTest {
|
||||
|
@ -2927,6 +2971,7 @@ class VaultRepositoryTest {
|
|||
private fun setupVaultDiskSourceFlows(
|
||||
ciphersFlow: Flow<List<SyncResponseJson.Cipher>> = bufferedMutableSharedFlow(),
|
||||
collectionsFlow: Flow<List<SyncResponseJson.Collection>> = bufferedMutableSharedFlow(),
|
||||
domainsFlow: Flow<SyncResponseJson.Domains> = bufferedMutableSharedFlow(),
|
||||
foldersFlow: Flow<List<SyncResponseJson.Folder>> = bufferedMutableSharedFlow(),
|
||||
sendsFlow: Flow<List<SyncResponseJson.Send>> = bufferedMutableSharedFlow(),
|
||||
) {
|
||||
|
@ -2934,6 +2979,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultDiskSource.getCollections(MOCK_USER_STATE.activeUserId)
|
||||
} returns collectionsFlow
|
||||
coEvery { vaultDiskSource.getDomains(MOCK_USER_STATE.activeUserId) } returns domainsFlow
|
||||
coEvery { vaultDiskSource.getFolders(MOCK_USER_STATE.activeUserId) } returns foldersFlow
|
||||
coEvery { vaultDiskSource.getSends(MOCK_USER_STATE.activeUserId) } returns sendsFlow
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Create a mock [DomainsData] with a given [number].
|
||||
*/
|
||||
fun createMockDomainsData(number: Int): DomainsData =
|
||||
DomainsData(
|
||||
equivalentDomains = listOf(listOf("mockEquivalentDomains-$number")),
|
||||
globalEquivalentDomains = listOf(
|
||||
DomainsData.GlobalEquivalentDomain(
|
||||
isExcluded = false,
|
||||
domains = listOf("domains-$number"),
|
||||
type = number,
|
||||
),
|
||||
),
|
||||
)
|
|
@ -0,0 +1,71 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DomainsExtensionsTest {
|
||||
@Test
|
||||
fun `toDomainsData returns populated DomainsData when domains are non-null`() {
|
||||
// Setup
|
||||
val equivalentDomains = listOf(listOf(EQUIVALENT_DOMAIN))
|
||||
val globalDomains = listOf(GLOBAL_EQUIVALENT_DOMAIN)
|
||||
val globalEquivalentDomain = SyncResponseJson.Domains.GlobalEquivalentDomain(
|
||||
isExcluded = false,
|
||||
domains = globalDomains,
|
||||
type = 0,
|
||||
)
|
||||
val domains = SyncResponseJson.Domains(
|
||||
equivalentDomains = equivalentDomains,
|
||||
globalEquivalentDomains = listOf(globalEquivalentDomain),
|
||||
)
|
||||
val expectedGlobalEquivalentDomain = DomainsData.GlobalEquivalentDomain(
|
||||
isExcluded = false,
|
||||
domains = globalDomains,
|
||||
type = 0,
|
||||
)
|
||||
val expected = DomainsData(
|
||||
equivalentDomains = equivalentDomains,
|
||||
globalEquivalentDomains = listOf(expectedGlobalEquivalentDomain),
|
||||
)
|
||||
|
||||
// Test
|
||||
val actual = domains.toDomainsData()
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toDomainsData returns empty DomainsData when domains are null`() {
|
||||
// Setup
|
||||
val globalEquivalentDomain = SyncResponseJson.Domains.GlobalEquivalentDomain(
|
||||
isExcluded = false,
|
||||
domains = null,
|
||||
type = 0,
|
||||
)
|
||||
val domains = SyncResponseJson.Domains(
|
||||
equivalentDomains = null,
|
||||
globalEquivalentDomains = listOf(globalEquivalentDomain),
|
||||
)
|
||||
val expectedGlobalEquivalentDomain = DomainsData.GlobalEquivalentDomain(
|
||||
isExcluded = false,
|
||||
domains = emptyList(),
|
||||
type = 0,
|
||||
)
|
||||
val expected = DomainsData(
|
||||
equivalentDomains = emptyList(),
|
||||
globalEquivalentDomains = listOf(expectedGlobalEquivalentDomain),
|
||||
)
|
||||
|
||||
// Test
|
||||
val actual = domains.toDomainsData()
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
private const val EQUIVALENT_DOMAIN: String = "EQUIVALENT_DOMAIN"
|
||||
private const val GLOBAL_EQUIVALENT_DOMAIN: String = "GLOBAL_EQUIVALENT_DOMAIN"
|
Loading…
Reference in a new issue