mirror of
https://github.com/bitwarden/android.git
synced 2024-11-26 19:36:18 +03:00
BIT-1205: Save login items (Encryption) (#295)
This commit is contained in:
parent
9fcd2b1690
commit
b279633166
15 changed files with 633 additions and 29 deletions
|
@ -21,6 +21,11 @@ interface VaultSdkSource {
|
|||
*/
|
||||
suspend fun initializeCrypto(request: InitUserCryptoRequest): Result<InitializeCryptoResult>
|
||||
|
||||
/**
|
||||
* Encrypts a [CipherView] returning a [Cipher] wrapped in a [Result].
|
||||
*/
|
||||
suspend fun encryptCipher(cipherView: CipherView): Result<Cipher>
|
||||
|
||||
/**
|
||||
* Decrypts a [Cipher] returning a [CipherView] wrapped in a [Result].
|
||||
*/
|
||||
|
|
|
@ -34,6 +34,9 @@ class VaultSdkSourceImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun encryptCipher(cipherView: CipherView): Result<Cipher> =
|
||||
runCatching { clientVault.ciphers().encrypt(cipherView) }
|
||||
|
||||
override suspend fun decryptCipher(cipher: Cipher): Result<CipherView> =
|
||||
runCatching { clientVault.ciphers().decrypt(cipher) }
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.bitwarden.core.CipherView
|
|||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
|
||||
|
@ -75,4 +76,9 @@ interface VaultRepository {
|
|||
privateKey: String,
|
||||
organizationalKeys: Map<String, String>,
|
||||
): VaultUnlockResult
|
||||
|
||||
/**
|
||||
* Attempt to create a cipher.
|
||||
*/
|
||||
suspend fun createCipher(cipherView: CipherView): CreateCipherResult
|
||||
}
|
||||
|
|
|
@ -14,12 +14,15 @@ import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
|||
import com.x8bit.bitwarden.data.platform.repository.util.map
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
|
||||
|
@ -44,6 +47,7 @@ import kotlinx.coroutines.launch
|
|||
@Suppress("TooManyFunctions")
|
||||
class VaultRepositoryImpl constructor(
|
||||
private val syncService: SyncService,
|
||||
private val ciphersService: CiphersService,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
|
@ -227,6 +231,25 @@ class VaultRepositoryImpl constructor(
|
|||
.onCompletion { willSyncAfterUnlock = false }
|
||||
.first()
|
||||
|
||||
override suspend fun createCipher(cipherView: CipherView): CreateCipherResult =
|
||||
vaultSdkSource
|
||||
.encryptCipher(cipherView = cipherView)
|
||||
.flatMap { cipher ->
|
||||
ciphersService
|
||||
.createCipher(
|
||||
body = cipher.toEncryptedNetworkCipher(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = {
|
||||
CreateCipherResult.Error
|
||||
},
|
||||
onSuccess = {
|
||||
sync()
|
||||
CreateCipherResult.Success
|
||||
},
|
||||
)
|
||||
|
||||
// TODO: This is temporary. Eventually this needs to be based on the presence of various
|
||||
// user keys but this will likely require SDK updates to support this (BIT-1190).
|
||||
private fun setVaultToUnlocked(userId: String) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository.di
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
|
@ -23,11 +24,13 @@ object VaultRepositoryModule {
|
|||
@Singleton
|
||||
fun providesVaultRepository(
|
||||
syncService: SyncService,
|
||||
ciphersService: CiphersService,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): VaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
ciphersService = ciphersService,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Models result of creating a cipher.
|
||||
*/
|
||||
sealed class CreateCipherResult {
|
||||
|
||||
/**
|
||||
* Cipher created successfully.
|
||||
*/
|
||||
data object Success : CreateCipherResult()
|
||||
|
||||
/**
|
||||
* Generic error while creating cipher.
|
||||
*/
|
||||
data object Error : CreateCipherResult()
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.x8bit.bitwarden.data.vault.repository.util
|
||||
|
||||
import CipherJsonRequest
|
||||
import com.bitwarden.core.Attachment
|
||||
import com.bitwarden.core.Card
|
||||
import com.bitwarden.core.Cipher
|
||||
|
@ -18,11 +20,205 @@ import com.bitwarden.core.UriMatchType
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherRepromptTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FieldTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.LinkedIdTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SecureNoteTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UriMatchTypeJson
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Cipher] object to a corresponding
|
||||
* [SyncResponseJson.Cipher] object.
|
||||
*/
|
||||
fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
|
||||
CipherJsonRequest(
|
||||
notes = notes,
|
||||
reprompt = reprompt.toNetworkRepromptType(),
|
||||
passwordHistory = passwordHistory?.toEncryptedNetworkPasswordHistoryList(),
|
||||
lastKnownRevisionDate = LocalDateTime.ofInstant(revisionDate, ZoneOffset.UTC),
|
||||
type = type.toNetworkCipherType(),
|
||||
login = login?.toEncryptedNetworkLogin(),
|
||||
secureNote = secureNote?.toEncryptedNetworkSecureNote(),
|
||||
folderId = folderId,
|
||||
organizationId = organizationId,
|
||||
identity = identity?.toEncryptedNetworkIdentity(),
|
||||
name = name,
|
||||
fields = fields?.toEncryptedNetworkFieldList(),
|
||||
isFavorite = favorite,
|
||||
card = card?.toEncryptedNetworkCard(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Card] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Card] object.
|
||||
*/
|
||||
private fun Card.toEncryptedNetworkCard(): SyncResponseJson.Cipher.Card =
|
||||
SyncResponseJson.Cipher.Card(
|
||||
number = number,
|
||||
expMonth = expMonth,
|
||||
code = code,
|
||||
expirationYear = expYear,
|
||||
cardholderName = cardholderName,
|
||||
brand = brand,
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a list of Bitwarden SDK [Field] objects to a corresponding
|
||||
* list of [SyncResponseJson.Cipher.Field] objects.
|
||||
*/
|
||||
private fun List<Field>.toEncryptedNetworkFieldList(): List<SyncResponseJson.Cipher.Field> =
|
||||
this.map { it.toEncryptedNetworkField() }
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Field] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Field] object.
|
||||
*/
|
||||
private fun Field.toEncryptedNetworkField(): SyncResponseJson.Cipher.Field =
|
||||
SyncResponseJson.Cipher.Field(
|
||||
linkedIdType = linkedId?.toNetworkLinkedIdType(),
|
||||
name = name,
|
||||
type = type.toNetworkFieldType(),
|
||||
value = value,
|
||||
)
|
||||
|
||||
private fun UInt.toNetworkLinkedIdType(): LinkedIdTypeJson =
|
||||
LinkedIdTypeJson.values().first { this == it.value }
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [FieldType] object to a corresponding
|
||||
* [FieldTypeJson] object.
|
||||
*/
|
||||
private fun FieldType.toNetworkFieldType(): FieldTypeJson =
|
||||
when (this) {
|
||||
FieldType.TEXT -> FieldTypeJson.TEXT
|
||||
FieldType.HIDDEN -> FieldTypeJson.HIDDEN
|
||||
FieldType.BOOLEAN -> FieldTypeJson.BOOLEAN
|
||||
FieldType.LINKED -> FieldTypeJson.LINKED
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Identity] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Identity] object.
|
||||
*/
|
||||
private fun Identity.toEncryptedNetworkIdentity(): SyncResponseJson.Cipher.Identity =
|
||||
SyncResponseJson.Cipher.Identity(
|
||||
title = title,
|
||||
middleName = middleName,
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
address1 = address1,
|
||||
address2 = address2,
|
||||
address3 = address3,
|
||||
city = city,
|
||||
state = state,
|
||||
postalCode = postalCode,
|
||||
country = country,
|
||||
company = company,
|
||||
email = email,
|
||||
phone = phone,
|
||||
ssn = ssn,
|
||||
username = username,
|
||||
passportNumber = passportNumber,
|
||||
licenseNumber = licenseNumber,
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [SecureNote] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.SecureNote] object.
|
||||
*/
|
||||
private fun SecureNote.toEncryptedNetworkSecureNote(): SyncResponseJson.Cipher.SecureNote =
|
||||
SyncResponseJson.Cipher.SecureNote(
|
||||
type = when (type) {
|
||||
SecureNoteType.GENERIC -> SecureNoteTypeJson.GENERIC
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a list of Bitwarden SDK [LoginUri] objects to a corresponding
|
||||
* list of [SyncResponseJson.Cipher.Login.Uri] objects.
|
||||
*/
|
||||
private fun List<LoginUri>.toEncryptedNetworkUriList(): List<SyncResponseJson.Cipher.Login.Uri> =
|
||||
this.map { it.toEncryptedNetworkUri() }
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [LoginUri] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Login.Uri] object.
|
||||
*/
|
||||
private fun LoginUri.toEncryptedNetworkUri(): SyncResponseJson.Cipher.Login.Uri =
|
||||
SyncResponseJson.Cipher.Login.Uri(
|
||||
uriMatchType = match?.toNetworkMatchType(),
|
||||
uri = uri,
|
||||
)
|
||||
|
||||
private fun UriMatchType.toNetworkMatchType(): UriMatchTypeJson =
|
||||
when (this) {
|
||||
UriMatchType.DOMAIN -> UriMatchTypeJson.DOMAIN
|
||||
UriMatchType.HOST -> UriMatchTypeJson.HOST
|
||||
UriMatchType.STARTS_WITH -> UriMatchTypeJson.STARTS_WITH
|
||||
UriMatchType.EXACT -> UriMatchTypeJson.EXACT
|
||||
UriMatchType.REGULAR_EXPRESSION -> UriMatchTypeJson.REGULAR_EXPRESSION
|
||||
UriMatchType.NEVER -> UriMatchTypeJson.NEVER
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Login] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Login] object.
|
||||
*/
|
||||
private fun Login.toEncryptedNetworkLogin(): SyncResponseJson.Cipher.Login =
|
||||
SyncResponseJson.Cipher.Login(
|
||||
uris = uris?.toEncryptedNetworkUriList(),
|
||||
totp = totp,
|
||||
password = password,
|
||||
passwordRevisionDate = passwordRevisionDate?.let {
|
||||
LocalDateTime.ofInstant(it, ZoneOffset.UTC)
|
||||
},
|
||||
shouldAutofillOnPageLoad = autofillOnPageLoad,
|
||||
uri = uris?.firstOrNull()?.uri,
|
||||
username = username,
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a list of Bitwarden SDK [PasswordHistory] objects to a corresponding
|
||||
* list of [SyncResponseJson.Cipher.PasswordHistory] objects.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
private fun List<PasswordHistory>.toEncryptedNetworkPasswordHistoryList(): List<SyncResponseJson.Cipher.PasswordHistory> =
|
||||
this.map { it.toEncryptedNetworkPasswordHistory() }
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [PasswordHistory] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.PasswordHistory] object.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
private fun PasswordHistory.toEncryptedNetworkPasswordHistory(): SyncResponseJson.Cipher.PasswordHistory =
|
||||
SyncResponseJson.Cipher.PasswordHistory(
|
||||
password = password,
|
||||
lastUsedDate = LocalDateTime.ofInstant(lastUsedDate, ZoneOffset.UTC),
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [CipherRepromptType] object to a corresponding
|
||||
* [CipherRepromptTypeJson] object.
|
||||
*/
|
||||
private fun CipherRepromptType.toNetworkRepromptType(): CipherRepromptTypeJson =
|
||||
when (this) {
|
||||
CipherRepromptType.NONE -> CipherRepromptTypeJson.NONE
|
||||
CipherRepromptType.PASSWORD -> CipherRepromptTypeJson.PASSWORD
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [CipherType] object to a corresponding
|
||||
* [CipherTypeJson] object.
|
||||
*/
|
||||
private fun CipherType.toNetworkCipherType(): CipherTypeJson =
|
||||
when (this) {
|
||||
CipherType.LOGIN -> CipherTypeJson.LOGIN
|
||||
CipherType.SECURE_NOTE -> CipherTypeJson.SECURE_NOTE
|
||||
CipherType.CARD -> CipherTypeJson.CARD
|
||||
CipherType.IDENTITY -> CipherTypeJson.IDENTITY
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of [SyncResponseJson.Cipher] objects to a list of corresponding
|
||||
* Bitwarden SDK [Cipher] objects.
|
||||
|
@ -31,7 +227,7 @@ fun List<SyncResponseJson.Cipher>.toEncryptedSdkCipherList(): List<Cipher> =
|
|||
map { it.toEncryptedSdkCipher() }
|
||||
|
||||
/**
|
||||
* Converts a of [SyncResponseJson.Cipher] object to a corresponding
|
||||
* Converts a [SyncResponseJson.Cipher] object to a corresponding
|
||||
* Bitwarden SDK [Cipher] object.
|
||||
*/
|
||||
fun SyncResponseJson.Cipher.toEncryptedSdkCipher(): Cipher =
|
||||
|
|
|
@ -4,10 +4,13 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState.ItemType.Card.displayStringResId
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState.ItemType.Identity.displayStringResId
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState.ItemType.SecureNotes.displayStringResId
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -31,6 +34,7 @@ private const val KEY_STATE = "state"
|
|||
@Suppress("TooManyFunctions")
|
||||
class VaultAddItemViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val vaultRepository: VaultRepository,
|
||||
) : BaseViewModel<VaultAddItemState, VaultAddItemEvent, VaultAddItemAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: INITIAL_STATE,
|
||||
) {
|
||||
|
@ -58,6 +62,10 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
is VaultAddItemAction.ItemType.LoginType -> {
|
||||
handleAddLoginTypeAction(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.Internal.CreateCipherResultReceive -> {
|
||||
handleCreateCipherResultReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,9 +75,11 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
|
||||
private fun handleSaveClick() {
|
||||
viewModelScope.launch {
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.ShowToast(
|
||||
message = "Save Item",
|
||||
sendAction(
|
||||
action = VaultAddItemAction.Internal.CreateCipherResultReceive(
|
||||
createCipherResult = vaultRepository.createCipher(
|
||||
cipherView = stateFlow.value.selectedType.toCipherView(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -106,6 +116,7 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
|
||||
//region Add Login Item Type Handlers
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun handleAddLoginTypeAction(
|
||||
action: VaultAddItemAction.ItemType.LoginType,
|
||||
) {
|
||||
|
@ -358,6 +369,30 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
|
||||
//endregion Add Login Item Type Handlers
|
||||
|
||||
//region Internal Type Handlers
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleCreateCipherResultReceive(action: VaultAddItemAction.Internal.CreateCipherResultReceive) {
|
||||
when (action.createCipherResult) {
|
||||
is CreateCipherResult.Error -> {
|
||||
// TODO Display error dialog BIT-501
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.ShowToast(
|
||||
message = "Save Item Failure",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is CreateCipherResult.Success -> {
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.NavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Internal Type Handlers
|
||||
|
||||
//region Utility Functions
|
||||
|
||||
private inline fun updateLoginType(
|
||||
|
@ -669,4 +704,17 @@ sealed class VaultAddItemAction {
|
|||
data object AddNewCustomFieldClick : LoginType()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions that the [VaultAddItemViewModel] itself might send.
|
||||
*/
|
||||
sealed class Internal : VaultAddItemAction() {
|
||||
|
||||
/**
|
||||
* Indicates a result for creating a cipher has been received.
|
||||
*/
|
||||
data class CreateCipherResultReceive(
|
||||
val createCipherResult: CreateCipherResult,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Transforms a [CipherView] into a [VaultState.ViewState.VaultItem].
|
||||
|
@ -74,3 +80,86 @@ fun VaultData.toViewState(): VaultState.ViewState =
|
|||
trashItemsCount = 0,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a [VaultAddItemState.ItemType] into [CipherView].
|
||||
*/
|
||||
fun VaultAddItemState.ItemType.toCipherView(): CipherView =
|
||||
when (this) {
|
||||
is VaultAddItemState.ItemType.Card -> toCardCipherView()
|
||||
is VaultAddItemState.ItemType.Identity -> toIdentityCipherView()
|
||||
is VaultAddItemState.ItemType.Login -> toLoginCipherView()
|
||||
is VaultAddItemState.ItemType.SecureNotes -> toSecureNotesCipherView()
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms [VaultAddItemState.ItemType.SecureNotes] into [CipherView].
|
||||
*/
|
||||
private fun VaultAddItemState.ItemType.SecureNotes.toSecureNotesCipherView(): CipherView =
|
||||
TODO("create SecureNotes CipherView BIT-509")
|
||||
|
||||
/**
|
||||
* Transforms [VaultAddItemState.ItemType.Login] into [CipherView].
|
||||
*/
|
||||
private fun VaultAddItemState.ItemType.Login.toLoginCipherView(): CipherView =
|
||||
CipherView(
|
||||
id = null,
|
||||
// TODO use real organization id BIT-780
|
||||
organizationId = null,
|
||||
// TODO use real folder id BIT-528
|
||||
folderId = null,
|
||||
collectionIds = emptyList(),
|
||||
key = null,
|
||||
name = name,
|
||||
notes = notes,
|
||||
type = CipherType.LOGIN,
|
||||
login = LoginView(
|
||||
username = username,
|
||||
password = password,
|
||||
passwordRevisionDate = null,
|
||||
uris = listOf(
|
||||
LoginUriView(
|
||||
uri = uri,
|
||||
// TODO implement uri settings in BIT-1094
|
||||
match = UriMatchType.DOMAIN,
|
||||
),
|
||||
),
|
||||
// TODO implement totp in BIT-1066
|
||||
totp = null,
|
||||
autofillOnPageLoad = false,
|
||||
),
|
||||
identity = null,
|
||||
card = null,
|
||||
secureNote = null,
|
||||
favorite = favorite,
|
||||
reprompt = if (masterPasswordReprompt) {
|
||||
CipherRepromptType.PASSWORD
|
||||
} else {
|
||||
CipherRepromptType.NONE
|
||||
},
|
||||
organizationUseTotp = false,
|
||||
edit = true,
|
||||
viewPassword = true,
|
||||
localData = null,
|
||||
attachments = null,
|
||||
// TODO implement custom fields BIT-529
|
||||
fields = null,
|
||||
passwordHistory = null,
|
||||
creationDate = Instant.now(),
|
||||
deletedDate = null,
|
||||
// This is a throw away value.
|
||||
// The SDK will eventually remove revisionDate via encryption.
|
||||
revisionDate = Instant.now(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Transforms [VaultAddItemState.ItemType.Identity] into [CipherView].
|
||||
*/
|
||||
private fun VaultAddItemState.ItemType.Identity.toIdentityCipherView(): CipherView =
|
||||
TODO("create Identity CipherView BIT-508")
|
||||
|
||||
/**
|
||||
* Transforms [VaultAddItemState.ItemType.Card] into [CipherView].
|
||||
*/
|
||||
private fun VaultAddItemState.ItemType.Card.toCardCipherView(): CipherView =
|
||||
TODO("create Card CipherView BIT-668")
|
||||
|
|
|
@ -99,6 +99,29 @@ class VaultSdkSourceTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decryptCipher should call SDK and return a Result with correct data`() = runBlocking {
|
||||
val mockCipher = mockk<CipherView>()
|
||||
val expectedResult = mockk<Cipher>()
|
||||
coEvery {
|
||||
clientVault.ciphers().encrypt(
|
||||
cipherView = mockCipher,
|
||||
)
|
||||
} returns expectedResult
|
||||
val result = vaultSdkSource.encryptCipher(
|
||||
cipherView = mockCipher,
|
||||
)
|
||||
assertEquals(
|
||||
expectedResult.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientVault.ciphers().encrypt(
|
||||
cipherView = mockCipher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Cipher decrypt should call SDK and return a Result with correct data`() = runBlocking {
|
||||
val mockCipher = mockk<Cipher>()
|
||||
|
|
|
@ -152,7 +152,7 @@ fun createMockPasswordHistoryView(number: Int): PasswordHistoryView =
|
|||
)
|
||||
|
||||
/**
|
||||
* Create a mock [SecureNoteView] with a given [number].
|
||||
* Create a mock [SecureNoteView].
|
||||
*/
|
||||
fun createMockSecureNoteView(): SecureNoteView =
|
||||
SecureNoteView(
|
||||
|
|
|
@ -16,7 +16,10 @@ import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
|||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest
|
||||
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
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
||||
|
@ -26,6 +29,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipher
|
|||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFolder
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSend
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
|
||||
|
@ -50,9 +54,11 @@ class VaultRepositoryTest {
|
|||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val syncService: SyncService = mockk()
|
||||
private val ciphersService: CiphersService = mockk()
|
||||
private val vaultSdkSource: VaultSdkSource = mockk()
|
||||
private val vaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
ciphersService = ciphersService,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
|
@ -1381,6 +1387,76 @@ class VaultRepositoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createCipher with encryptCipher failure should return CreateCipherResult failure`() =
|
||||
runTest {
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(cipherView = mockCipherView)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createCipher(cipherView = mockCipherView)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Error,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createCipher with ciphersService createCipher failure should return CreateCipherResult failure`() = runTest {
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(cipherView = mockCipherView)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.createCipher(
|
||||
body = createMockCipherJsonRequest(number = 1),
|
||||
)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createCipher(cipherView = mockCipherView)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Error,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createCipher with ciphersService createCipher success should return CreateCipherResult success`() = runTest {
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(cipherView = mockCipherView)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.createCipher(
|
||||
body = createMockCipherJsonRequest(number = 1),
|
||||
)
|
||||
} returns createMockCipher(number = 1).asSuccess()
|
||||
coEvery {
|
||||
syncService.sync()
|
||||
} returns Result.success(createMockSyncResponse(1))
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
|
||||
} returns listOf(createMockCipherView(1)).asSuccess()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1)))
|
||||
} returns listOf(createMockFolderView(1)).asSuccess()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSendList(listOf(createMockSdkSend(1)))
|
||||
} returns listOf(createMockSendView(1)).asSuccess()
|
||||
|
||||
val result = vaultRepository.createCipher(cipherView = mockCipherView)
|
||||
|
||||
assertEquals(
|
||||
CreateCipherResult.Success,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
//region Helper functions
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.UriMatchTypeJson
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachment
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCard
|
||||
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.createMockField
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockIdentity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockLogin
|
||||
|
@ -31,6 +32,16 @@ import org.junit.Test
|
|||
|
||||
class VaultSdkCipherExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toEncryptedNetworkCipher should convert an Sdk Cipher to a Network Cipher`() {
|
||||
val sdkCipher = createMockSdkCipher(number = 1)
|
||||
val syncCipher = sdkCipher.toEncryptedNetworkCipher()
|
||||
assertEquals(
|
||||
createMockCipherJsonRequest(number = 1),
|
||||
syncCipher,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toEncryptedSdkCipherList should convert list of Network Cipher to List of Sdk Cipher`() {
|
||||
val syncCiphers = listOf(
|
||||
|
|
|
@ -2,7 +2,11 @@ package com.x8bit.bitwarden.ui.vault.feature.additem
|
|||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
@ -13,10 +17,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
private val initialState = createVaultAddLoginItemState()
|
||||
private val initialSavedStateHandle = createSavedStateHandleWithState(initialState)
|
||||
private val vaultRepository: VaultRepository = mockk()
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
}
|
||||
|
@ -24,7 +29,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `CloseClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddItemAction.CloseClick)
|
||||
assertEquals(VaultAddItemEvent.NavigateBack, awaitItem())
|
||||
|
@ -32,17 +37,31 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick should emit ShowToast`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
fun `SaveClick createCipher success should emit NavigateBack`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
coEvery {
|
||||
vaultRepository.createCipher(any())
|
||||
} returns CreateCipherResult.Success
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick)
|
||||
assertEquals(VaultAddItemEvent.ShowToast("Save Item"), awaitItem())
|
||||
assertEquals(VaultAddItemEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick createCipher error should emit ShowToast`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
coEvery {
|
||||
vaultRepository.createCipher(any())
|
||||
} returns CreateCipherResult.Error
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick)
|
||||
assertEquals(VaultAddItemEvent.ShowToast("Save Item Failure"), awaitItem())
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun `TypeOptionSelect LOGIN should switch to LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.TypeOptionSelect(VaultAddItemState.ItemTypeOption.LOGIN)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -58,12 +77,12 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
viewModel = createAddVaultItemViewModel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NameTextChange should update name in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.NameTextChange("newName")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -80,7 +99,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UsernameTextChange should update username in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.UsernameTextChange("newUsername")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -97,7 +116,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `PasswordTextChange should update password in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.PasswordTextChange("newPassword")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -113,7 +132,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `UriTextChange should update uri in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.UriTextChange("newUri")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -129,7 +148,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `FolderChange should update folder in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.FolderChange("newFolder")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -145,7 +164,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `ToggleFavorite should update favorite in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.ToggleFavorite(true)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -163,7 +182,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `ToggleMasterPasswordReprompt should update masterPasswordReprompt in LoginItem`() =
|
||||
runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt(
|
||||
isMasterPasswordReprompt = true,
|
||||
)
|
||||
|
@ -181,7 +200,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `NotesTextChange should update notes in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action = VaultAddItemAction.ItemType.LoginType.NotesTextChange(notes = "newNotes")
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
@ -198,7 +217,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `OwnershipChange should update ownership in LoginItem`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
val action =
|
||||
VaultAddItemAction.ItemType.LoginType.OwnershipChange(ownership = "newOwner")
|
||||
|
||||
|
@ -217,7 +236,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `OpenUsernameGeneratorClick should emit ShowToast with 'Open Username Generator' message`() =
|
||||
runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(
|
||||
|
@ -233,7 +252,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `PasswordCheckerClick should emit ShowToast with 'Password Checker' message`() =
|
||||
runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
|
@ -248,7 +267,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `OpenPasswordGeneratorClick should emit ShowToast with 'Open Password Generator' message`() =
|
||||
runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
|
@ -265,7 +284,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `SetupTotpClick should emit ShowToast with 'Setup TOTP' message`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddItemAction.ItemType.LoginType.SetupTotpClick)
|
||||
|
@ -276,7 +295,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UriSettingsClick should emit ShowToast with 'URI Settings' message`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddItemAction.ItemType.LoginType.UriSettingsClick)
|
||||
|
@ -286,7 +305,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `AddNewUriClick should emit ShowToast with 'Add New URI' message`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
|
@ -301,7 +320,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `TooltipClick should emit ShowToast with 'Tooltip' message`() = runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
|
@ -316,7 +335,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `AddNewCustomFieldClick should emit ShowToast with 'Add New Custom Field' message`() =
|
||||
runTest {
|
||||
val viewModel = VaultAddItemViewModel(initialSavedStateHandle)
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
|
@ -359,4 +378,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
SavedStateHandle().apply {
|
||||
set("state", state)
|
||||
}
|
||||
|
||||
private fun createAddVaultItemViewModel(): VaultAddItemViewModel =
|
||||
VaultAddItemViewModel(
|
||||
savedStateHandle = initialSavedStateHandle,
|
||||
vaultRepository = vaultRepository,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Instant
|
||||
|
||||
class VaultDataExtensionsTest {
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
// Some individual tests call mockkStatic so we will make sure this is always undone.
|
||||
unmockkStatic(Instant::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState should transform full VaultData into ViewState Content`() {
|
||||
val vaultData = VaultData(
|
||||
|
@ -84,4 +102,65 @@ class VaultDataExtensionsTest {
|
|||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform Login ItemType to CipherView`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val loginItemType = VaultAddItemState.ItemType.Login(
|
||||
name = "mockName-1",
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
uri = "mockUri-1",
|
||||
folder = "mockFolder-1",
|
||||
favorite = false,
|
||||
masterPasswordReprompt = false,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
)
|
||||
|
||||
val result = loginItemType.toCipherView()
|
||||
|
||||
assertEquals(
|
||||
CipherView(
|
||||
id = null,
|
||||
organizationId = null,
|
||||
folderId = null,
|
||||
collectionIds = emptyList(),
|
||||
key = null,
|
||||
name = "mockName-1",
|
||||
notes = "mockNotes-1",
|
||||
type = CipherType.LOGIN,
|
||||
login = LoginView(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
passwordRevisionDate = null,
|
||||
uris = listOf(
|
||||
LoginUriView(
|
||||
uri = "mockUri-1",
|
||||
match = UriMatchType.DOMAIN,
|
||||
),
|
||||
),
|
||||
totp = null,
|
||||
autofillOnPageLoad = false,
|
||||
),
|
||||
identity = null,
|
||||
card = null,
|
||||
secureNote = null,
|
||||
favorite = false,
|
||||
reprompt = CipherRepromptType.NONE,
|
||||
organizationUseTotp = false,
|
||||
edit = true,
|
||||
viewPassword = true,
|
||||
localData = null,
|
||||
attachments = null,
|
||||
fields = null,
|
||||
passwordHistory = null,
|
||||
creationDate = Instant.MIN,
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue