mirror of
https://github.com/bitwarden/android.git
synced 2024-11-23 18:06:08 +03:00
BIT-1408: Delete cipher (#691)
This commit is contained in:
parent
c9d7a48598
commit
d706a20211
15 changed files with 510 additions and 102 deletions
|
@ -39,10 +39,18 @@ interface CiphersApi {
|
|||
): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Deletes a cipher.
|
||||
* Hard deletes a cipher.
|
||||
*/
|
||||
@DELETE("ciphers/{cipherId}")
|
||||
suspend fun deleteCipher(
|
||||
suspend fun hardDeleteCipher(
|
||||
@Path("cipherId") cipherId: String,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Soft deletes a cipher.
|
||||
*/
|
||||
@PUT("ciphers/{cipherId}/delete")
|
||||
suspend fun softDeleteCipher(
|
||||
@Path("cipherId") cipherId: String,
|
||||
): Result<Unit>
|
||||
}
|
||||
|
|
|
@ -31,7 +31,12 @@ interface CiphersService {
|
|||
): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
* Attempt to hard delete a cipher.
|
||||
*/
|
||||
suspend fun deleteCipher(cipherId: String): Result<Unit>
|
||||
suspend fun hardDeleteCipher(cipherId: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Attempt to soft delete a cipher.
|
||||
*/
|
||||
suspend fun softDeleteCipher(cipherId: String): Result<Unit>
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@ class CiphersServiceImpl constructor(
|
|||
body = body,
|
||||
)
|
||||
|
||||
override suspend fun deleteCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.deleteCipher(cipherId = cipherId)
|
||||
override suspend fun hardDeleteCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.hardDeleteCipher(cipherId = cipherId)
|
||||
|
||||
override suspend fun softDeleteCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.softDeleteCipher(cipherId = cipherId)
|
||||
}
|
||||
|
|
|
@ -162,7 +162,15 @@ interface VaultRepository : VaultLockManager {
|
|||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
suspend fun deleteCipher(cipherId: String): DeleteCipherResult
|
||||
suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to soft delete a cipher.
|
||||
*/
|
||||
suspend fun softDeleteCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to update a cipher.
|
||||
|
|
|
@ -48,6 +48,7 @@ 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.toEncryptedNetworkCipher
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherResponse
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
|
||||
|
@ -71,6 +72,7 @@ import kotlinx.coroutines.flow.onStart
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* A "stop timeout delay" in milliseconds used to let a shared coroutine continue to run for the
|
||||
|
@ -383,10 +385,10 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteCipher(cipherId: String): DeleteCipherResult {
|
||||
override suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult {
|
||||
val userId = requireNotNull(activeUserId)
|
||||
return ciphersService
|
||||
.deleteCipher(cipherId)
|
||||
.hardDeleteCipher(cipherId)
|
||||
.onSuccess { vaultDiskSource.deleteCipher(userId, cipherId) }
|
||||
.fold(
|
||||
onSuccess = { DeleteCipherResult.Success },
|
||||
|
@ -394,6 +396,34 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun softDeleteCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteCipherResult {
|
||||
val userId = requireNotNull(activeUserId)
|
||||
return ciphersService
|
||||
.softDeleteCipher(cipherId)
|
||||
.fold(
|
||||
onSuccess = {
|
||||
vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(
|
||||
deletedDate = Instant.now(),
|
||||
),
|
||||
)
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
DeleteCipherResult.Success
|
||||
},
|
||||
onFailure = { DeleteCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
|
|
|
@ -49,6 +49,37 @@ fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
|
|||
card = card?.toEncryptedNetworkCard(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Cipher] object to a corresponding
|
||||
* [SyncResponseJson.Cipher] object.
|
||||
*/
|
||||
fun Cipher.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
|
||||
SyncResponseJson.Cipher(
|
||||
notes = notes,
|
||||
reprompt = reprompt.toNetworkRepromptType(),
|
||||
passwordHistory = passwordHistory?.toEncryptedNetworkPasswordHistoryList(),
|
||||
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(),
|
||||
attachments = attachments?.toNetworkAttachmentList(),
|
||||
shouldOrganizationUseTotp = organizationUseTotp,
|
||||
shouldEdit = edit,
|
||||
revisionDate = ZonedDateTime.ofInstant(revisionDate, ZoneOffset.UTC),
|
||||
creationDate = ZonedDateTime.ofInstant(creationDate, ZoneOffset.UTC),
|
||||
deletedDate = deletedDate?.let { ZonedDateTime.ofInstant(it, ZoneOffset.UTC) },
|
||||
collectionIds = collectionIds,
|
||||
id = id.orEmpty(),
|
||||
shouldViewPassword = viewPassword,
|
||||
key = key,
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Card] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Card] object.
|
||||
|
@ -161,6 +192,27 @@ private fun UriMatchType.toNetworkMatchType(): UriMatchTypeJson =
|
|||
UriMatchType.NEVER -> UriMatchTypeJson.NEVER
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of Bitwarden SDK [Attachment] objects to a corresponding
|
||||
* [SyncResponseJson.Cipher.Attachment] list.
|
||||
*/
|
||||
private fun List<Attachment>.toNetworkAttachmentList(): List<SyncResponseJson.Cipher.Attachment> =
|
||||
map { it.toNetworkAttachment() }
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Attachment] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Attachment] object.
|
||||
*/
|
||||
private fun Attachment.toNetworkAttachment(): SyncResponseJson.Cipher.Attachment =
|
||||
SyncResponseJson.Cipher.Attachment(
|
||||
fileName = fileName,
|
||||
size = size?.toInt() ?: 0,
|
||||
sizeName = sizeName,
|
||||
id = id,
|
||||
url = url,
|
||||
key = key,
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts a Bitwarden SDK [Login] object to a corresponding
|
||||
* [SyncResponseJson.Cipher.Login] object.
|
||||
|
|
|
@ -15,7 +15,10 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -27,7 +30,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
|
@ -37,6 +39,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenMasterPasswordDialog
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
|
@ -62,6 +65,11 @@ fun VaultItemScreen(
|
|||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
val confirmDeleteClickAction = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemAction.Common.ConfirmDeleteClick) }
|
||||
}
|
||||
var pendingDeleteCipher by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
VaultItemEvent.NavigateBack -> onNavigateBack()
|
||||
|
@ -103,6 +111,25 @@ fun VaultItemScreen(
|
|||
},
|
||||
)
|
||||
|
||||
if (pendingDeleteCipher) {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.delete),
|
||||
message = stringResource(id = R.string.do_you_really_want_to_soft_delete_cipher),
|
||||
confirmButtonText = stringResource(id = R.string.ok),
|
||||
dismissButtonText = stringResource(id = R.string.cancel),
|
||||
onConfirmClick = {
|
||||
pendingDeleteCipher = false
|
||||
confirmDeleteClickAction()
|
||||
},
|
||||
onDismissClick = {
|
||||
pendingDeleteCipher = false
|
||||
},
|
||||
onDismissRequest = {
|
||||
pendingDeleteCipher = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
|
@ -123,9 +150,7 @@ fun VaultItemScreen(
|
|||
menuItemDataList = persistentListOf(
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.delete),
|
||||
onClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemAction.Common.DeleteClick) }
|
||||
},
|
||||
onClick = { pendingDeleteCipher = true },
|
||||
),
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.attachments),
|
||||
|
@ -213,8 +238,8 @@ private fun VaultItemDialogs(
|
|||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
|
||||
VaultItemState.DialogState.Loading -> BitwardenLoadingDialog(
|
||||
visibilityState = LoadingDialogState.Shown(text = R.string.loading.asText()),
|
||||
is VaultItemState.DialogState.Loading -> BitwardenLoadingDialog(
|
||||
visibilityState = LoadingDialogState.Shown(text = dialog.message),
|
||||
)
|
||||
|
||||
VaultItemState.DialogState.MasterPasswordDialog -> {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.item
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.CipherView
|
||||
|
@ -12,6 +11,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
|||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VerifyPasswordResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -67,7 +68,6 @@ class VaultItemViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun handleAction(action: VaultItemAction) {
|
||||
Log.d("ramsey", "handleAction: action $action")
|
||||
when (action) {
|
||||
is VaultItemAction.ItemType.Login -> handleLoginTypeActions(action)
|
||||
is VaultItemAction.ItemType.Card -> handleCardTypeActions(action)
|
||||
|
@ -100,8 +100,8 @@ class VaultItemViewModel @Inject constructor(
|
|||
|
||||
is VaultItemAction.Common.AttachmentsClick -> handleAttachmentsClick()
|
||||
is VaultItemAction.Common.CloneClick -> handleCloneClick()
|
||||
is VaultItemAction.Common.DeleteClick -> handleDeleteClick()
|
||||
is VaultItemAction.Common.MoveToOrganizationClick -> handleMoveToOrganizationClick()
|
||||
is VaultItemAction.Common.ConfirmDeleteClick -> handleConfirmDeleteClick()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ class VaultItemViewModel @Inject constructor(
|
|||
|
||||
private fun handleMasterPasswordSubmit(action: VaultItemAction.Common.MasterPasswordSubmit) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultItemState.DialogState.Loading)
|
||||
it.copy(dialog = VaultItemState.DialogState.Loading(R.string.loading.asText()))
|
||||
}
|
||||
viewModelScope.launch {
|
||||
@Suppress("MagicNumber")
|
||||
|
@ -215,15 +215,37 @@ class VaultItemViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleDeleteClick() {
|
||||
// TODO Implement delete in BIT-1408
|
||||
sendEvent(VaultItemEvent.ShowToast("Not yet implemented.".asText()))
|
||||
}
|
||||
|
||||
private fun handleMoveToOrganizationClick() {
|
||||
sendEvent(VaultItemEvent.NavigateToMoveToOrganization(itemId = state.vaultItemId))
|
||||
}
|
||||
|
||||
private fun handleConfirmDeleteClick() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
R.string.soft_deleting.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
onContent { content ->
|
||||
content
|
||||
.common
|
||||
.currentCipher
|
||||
?.let { cipher ->
|
||||
viewModelScope.launch {
|
||||
trySendAction(
|
||||
VaultItemAction.Internal.DeleteCipherReceive(
|
||||
result = vaultRepository.softDeleteCipher(
|
||||
cipherId = state.vaultItemId,
|
||||
cipherView = cipher,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Common Handlers
|
||||
|
||||
//region Login Type Handlers
|
||||
|
@ -264,7 +286,7 @@ class VaultItemViewModel @Inject constructor(
|
|||
onLoginContent { _, login ->
|
||||
val password = requireNotNull(login.passwordData?.password)
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultItemState.DialogState.Loading)
|
||||
it.copy(dialog = VaultItemState.DialogState.Loading(R.string.loading.asText()))
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = authRepository.getPasswordBreachCount(password = password)
|
||||
|
@ -391,6 +413,7 @@ class VaultItemViewModel @Inject constructor(
|
|||
is VaultItemAction.Internal.PasswordBreachReceive -> handlePasswordBreachReceive(action)
|
||||
is VaultItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action)
|
||||
is VaultItemAction.Internal.VerifyPasswordReceive -> handleVerifyPasswordReceive(action)
|
||||
is VaultItemAction.Internal.DeleteCipherReceive -> handleDeleteCipherReceive(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,6 +526,25 @@ class VaultItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteCipherReceive(action: VaultItemAction.Internal.DeleteCipherReceive) {
|
||||
when (action.result) {
|
||||
DeleteCipherResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultItemState.DialogState.Generic(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
DeleteCipherResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialog = null) }
|
||||
sendEvent(VaultItemEvent.ShowToast(message = R.string.item_soft_deleted.asText()))
|
||||
sendEvent(VaultItemEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Internal Type Handlers
|
||||
|
||||
private inline fun onContent(
|
||||
|
@ -589,6 +631,7 @@ data class VaultItemState(
|
|||
* @property customFields A list of custom fields that user has added.
|
||||
* @property requiresReprompt Indicates if a master password prompt is required to view
|
||||
* secure fields.
|
||||
* @property currentCipher The cipher that is currently being viewed (nullable).
|
||||
*/
|
||||
@Parcelize
|
||||
data class Common(
|
||||
|
@ -597,6 +640,8 @@ data class VaultItemState(
|
|||
val notes: String?,
|
||||
val customFields: List<Custom>,
|
||||
val requiresReprompt: Boolean,
|
||||
@IgnoredOnParcel
|
||||
val currentCipher: CipherView? = null,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
@ -766,10 +811,12 @@ data class VaultItemState(
|
|||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Displays the loading dialog to the user.
|
||||
* Displays the loading dialog to the user with a message.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Loading : DialogState()
|
||||
data class Loading(
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Displays the master password dialog to the user.
|
||||
|
@ -848,6 +895,11 @@ sealed class VaultItemAction {
|
|||
*/
|
||||
data object CloseClick : Common()
|
||||
|
||||
/**
|
||||
* The user has confirmed to deleted the cipher.
|
||||
*/
|
||||
data object ConfirmDeleteClick : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked to dismiss the dialog.
|
||||
*/
|
||||
|
@ -892,11 +944,6 @@ sealed class VaultItemAction {
|
|||
val isVisible: Boolean,
|
||||
) : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked the delete button.
|
||||
*/
|
||||
data object DeleteClick : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked the attachments button.
|
||||
*/
|
||||
|
@ -1006,5 +1053,12 @@ sealed class VaultItemAction {
|
|||
data class VerifyPasswordReceive(
|
||||
val result: VerifyPasswordResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the delete cipher result has been received.
|
||||
*/
|
||||
data class DeleteCipherReceive(
|
||||
val result: DeleteCipherResult,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ fun CipherView.toViewState(
|
|||
): VaultItemState.ViewState =
|
||||
VaultItemState.ViewState.Content(
|
||||
common = VaultItemState.ViewState.Content.Common(
|
||||
currentCipher = this,
|
||||
name = name,
|
||||
requiresReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
customFields = fields.orEmpty().map { it.toCustomField() },
|
||||
|
|
|
@ -66,10 +66,18 @@ class CiphersServiceTest : BaseServiceTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `deleteCipher should execute the delete cipher API`() = runTest {
|
||||
fun `hardDeleteCipher should execute the hardDeleteCipher API`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
val cipherId = "cipherId"
|
||||
val result = ciphersService.deleteCipher(cipherId = cipherId)
|
||||
val result = ciphersService.hardDeleteCipher(cipherId = cipherId)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `softDeleteCipher should execute the softDeleteCipher API`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
val cipherId = "cipherId"
|
||||
val result = ciphersService.softDeleteCipher(cipherId = cipherId)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository
|
|||
|
||||
import android.net.Uri
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.Cipher
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.DateTime
|
||||
|
@ -68,6 +69,7 @@ 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.util.toEncryptedNetworkCipherResponse
|
||||
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
|
||||
|
@ -90,6 +92,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
|||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.net.UnknownHostException
|
||||
import java.time.Instant
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class VaultRepositoryTest {
|
||||
|
@ -1549,34 +1552,95 @@ class VaultRepositoryTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `deleteCipher with ciphersService deleteCipher failure should return DeleteCipherResult Error`() =
|
||||
fun `hardDeleteCipher with ciphersService hardDeleteCipher failure should return DeleteCipherResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val cipherId = "mockId-1"
|
||||
coEvery {
|
||||
ciphersService.deleteCipher(cipherId = cipherId)
|
||||
ciphersService.hardDeleteCipher(cipherId = cipherId)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = vaultRepository.deleteCipher(cipherId)
|
||||
val result = vaultRepository.hardDeleteCipher(cipherId)
|
||||
|
||||
assertEquals(DeleteCipherResult.Error, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `deleteCipher with ciphersService deleteCipher success should return DeleteCipherResult success`() =
|
||||
fun `hardDeleteCipher with ciphersService hardDeleteCipher success should return DeleteCipherResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val cipherId = "mockId-1"
|
||||
coEvery { ciphersService.deleteCipher(cipherId = cipherId) } returns Unit.asSuccess()
|
||||
coEvery { ciphersService.hardDeleteCipher(cipherId = cipherId) } returns Unit.asSuccess()
|
||||
coEvery { vaultDiskSource.deleteCipher(userId, cipherId) } just runs
|
||||
|
||||
val result = vaultRepository.deleteCipher(cipherId)
|
||||
val result = vaultRepository.hardDeleteCipher(cipherId)
|
||||
|
||||
assertEquals(DeleteCipherResult.Success, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softDeleteCipher with ciphersService softDeleteCipher failure should return DeleteCipherResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val cipherId = "mockId-1"
|
||||
coEvery {
|
||||
ciphersService.softDeleteCipher(cipherId = cipherId)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = vaultRepository.softDeleteCipher(
|
||||
cipherId = cipherId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
|
||||
assertEquals(DeleteCipherResult.Error, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `softDeleteCipher with ciphersService softDeleteCipher success should return DeleteCipherResult success`() =
|
||||
runTest {
|
||||
mockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
every {
|
||||
createMockSdkCipher(number = 1).toEncryptedNetworkCipherResponse()
|
||||
} returns createMockCipher(number = 1)
|
||||
val fixedInstant = Instant.parse("2021-01-01T00:00:00Z")
|
||||
val userId = "mockId-1"
|
||||
val cipherId = "mockId-1"
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1)
|
||||
.copy(
|
||||
deletedDate = fixedInstant,
|
||||
),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery { ciphersService.softDeleteCipher(cipherId = cipherId) } returns Unit.asSuccess()
|
||||
coEvery { vaultDiskSource.deleteCipher(userId, cipherId) } just runs
|
||||
coEvery {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = createMockCipher(number = 1),
|
||||
)
|
||||
} returns Unit
|
||||
val cipherView = createMockCipherView(number = 1)
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns fixedInstant
|
||||
|
||||
val result = vaultRepository.softDeleteCipher(
|
||||
cipherId = cipherId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
|
||||
assertEquals(DeleteCipherResult.Success, result)
|
||||
unmockkStatic(Instant::class)
|
||||
unmockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createSend with encryptSend failure should return CreateSendResult failure`() =
|
||||
runTest {
|
||||
|
@ -2297,6 +2361,12 @@ class VaultRepositoryTest {
|
|||
return mockUri
|
||||
}
|
||||
|
||||
private fun setupMockInstant(): Instant {
|
||||
val mockInstant = mockk<Instant>()
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
return mockInstant
|
||||
}
|
||||
|
||||
//endregion Helper functions
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,18 @@ import org.junit.Test
|
|||
|
||||
class VaultSdkCipherExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toEncryptedNetworkCipherResponse should convert an Sdk Cipher to a cipher`() {
|
||||
val sdkCipher = createMockSdkCipher(number = 1)
|
||||
|
||||
val result = sdkCipher.toEncryptedNetworkCipherResponse()
|
||||
|
||||
assertEquals(
|
||||
createMockCipher(number = 1),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toEncryptedNetworkCipher should convert an Sdk Cipher to a Network Cipher`() {
|
||||
val sdkCipher = createMockSdkCipher(number = 1)
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.ui.test.assert
|
|||
import androidx.compose.ui.test.assertCountEquals
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.assertTextContains
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
|
@ -23,6 +24,7 @@ import androidx.compose.ui.test.onSiblings
|
|||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -156,7 +158,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
composeTestRule.onNodeWithText("Loading").assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultItemState.DialogState.Loading)
|
||||
it.copy(dialog = VaultItemState.DialogState.Loading(R.string.loading.asText()))
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
|
@ -558,21 +560,74 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `Delete option menu click should send DeleteClick action`() {
|
||||
fun `menu Delete option click should send show deletion confirmation dialog`() {
|
||||
// Confirm dropdown version of item is absent
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Delete")
|
||||
.filter(hasAnyAncestor(isPopup()))
|
||||
.assertCountEquals(0)
|
||||
// Open the overflow menu
|
||||
composeTestRule.onNodeWithContentDescription("More").performClick()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
// Click on the delete item in the dropdown
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Delete")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Do you really want to send to the trash?")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Delete dialog cancel click should hide deletion confirmation menu`() {
|
||||
// Open the overflow menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
// Click on the delete item in the dropdown
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Delete")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Do you really want to send to the trash?")
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Cancel")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Do you really want to send to the trash?")
|
||||
.assertIsNotDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Delete dialog ok click should send ConfirmDeleteClick`() {
|
||||
// Open the overflow menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
// Click on the delete item in the dropdown
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Delete")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Do you really want to send to the trash?")
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Ok")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultItemAction.Common.DeleteClick)
|
||||
viewModel.trySendAction(VaultItemAction.Common.ConfirmDeleteClick)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
|||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.util.createCommonContent
|
||||
|
@ -97,7 +99,11 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `on DismissDialogClick should clear the dialog state`() = runTest {
|
||||
val initialState = DEFAULT_STATE.copy(dialog = VaultItemState.DialogState.Loading)
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel(state = initialState)
|
||||
assertEquals(initialState, viewModel.stateFlow.value)
|
||||
|
||||
|
@ -105,6 +111,65 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(initialState.copy(dialog = null), viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `ConfirmDeleteClick with DeleteCipherResult Success should should ShowToast and NavigateBack`() =
|
||||
runTest {
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
val viewModel = createViewModel(state = DEFAULT_STATE)
|
||||
coEvery {
|
||||
vaultRepo.softDeleteCipher(
|
||||
cipherId = VAULT_ITEM_ID,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns DeleteCipherResult.Success
|
||||
|
||||
viewModel.trySendAction(VaultItemAction.Common.ConfirmDeleteClick)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
VaultItemEvent.ShowToast(R.string.item_soft_deleted.asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
VaultItemEvent.NavigateBack,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `ConfirmDeleteClick with DeleteCipherResult Failure should should Show generic error`() =
|
||||
runTest {
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every { toViewState(isPremiumUser = true) } returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
val viewModel = createViewModel(state = DEFAULT_STATE)
|
||||
coEvery {
|
||||
vaultRepo.softDeleteCipher(
|
||||
cipherId = VAULT_ITEM_ID,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns DeleteCipherResult.Error
|
||||
|
||||
viewModel.trySendAction(VaultItemAction.Common.ConfirmDeleteClick)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_VIEW_STATE,
|
||||
dialog = VaultItemState.DialogState.Generic(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on EditClick should do nothing when ViewState is not Content`() = runTest {
|
||||
val initialState = DEFAULT_STATE
|
||||
|
@ -175,7 +240,11 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(loginState, awaitItem())
|
||||
viewModel.trySendAction(VaultItemAction.Common.MasterPasswordSubmit("password"))
|
||||
assertEquals(
|
||||
loginState.copy(dialog = VaultItemState.DialogState.Loading),
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
|
@ -298,7 +367,9 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isVisible = false,
|
||||
)
|
||||
val loginViewState = VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = true).copy(
|
||||
common = createCommonContent(
|
||||
isEmpty = true,
|
||||
).copy(
|
||||
requiresReprompt = false,
|
||||
customFields = listOf(hiddenField),
|
||||
),
|
||||
|
@ -334,18 +405,6 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on DeleteClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel(state = DEFAULT_STATE)
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultItemAction.Common.DeleteClick)
|
||||
assertEquals(
|
||||
VaultItemEvent.ShowToast("Not yet implemented.".asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on AttachmentsClick should emit NavigateToAttachments`() = runTest {
|
||||
val viewModel = createViewModel(state = DEFAULT_STATE)
|
||||
|
@ -413,7 +472,11 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(loginState, awaitItem())
|
||||
viewModel.trySendAction(VaultItemAction.ItemType.Login.CheckForBreachClick)
|
||||
assertEquals(
|
||||
loginState.copy(dialog = VaultItemState.DialogState.Loading),
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
|
@ -864,6 +927,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
currentCipher = createMockCipherView(number = 1),
|
||||
)
|
||||
|
||||
private val DEFAULT_VIEW_STATE: VaultItemState.ViewState.Content =
|
||||
|
|
|
@ -24,12 +24,12 @@ class CipherViewExtensionsTest {
|
|||
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content with premium`() {
|
||||
val viewState = createCipherView(type = CipherType.LOGIN, isEmpty = false)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val cipherView = createCipherView(type = CipherType.LOGIN, isEmpty = false)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false),
|
||||
common = createCommonContent(isEmpty = false).copy(currentCipher = cipherView),
|
||||
type = createLoginContent(isEmpty = false),
|
||||
),
|
||||
viewState,
|
||||
|
@ -40,12 +40,12 @@ class CipherViewExtensionsTest {
|
|||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content without premium`() {
|
||||
val isPremiumUser = false
|
||||
val viewState = createCipherView(type = CipherType.LOGIN, isEmpty = false)
|
||||
.toViewState(isPremiumUser = isPremiumUser)
|
||||
val cipherView = createCipherView(type = CipherType.LOGIN, isEmpty = false)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = isPremiumUser)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false),
|
||||
common = createCommonContent(isEmpty = false).copy(currentCipher = cipherView),
|
||||
type = createLoginContent(isEmpty = false).copy(isPremiumUser = isPremiumUser),
|
||||
),
|
||||
viewState,
|
||||
|
@ -54,12 +54,14 @@ class CipherViewExtensionsTest {
|
|||
|
||||
@Test
|
||||
fun `toViewState should transform empty CipherView into ViewState Login Content`() {
|
||||
val viewState = createCipherView(type = CipherType.LOGIN, isEmpty = true)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val cipherView = createCipherView(type = CipherType.LOGIN, isEmpty = true)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = true),
|
||||
common = createCommonContent(isEmpty = true).copy(
|
||||
currentCipher = cipherView,
|
||||
),
|
||||
type = createLoginContent(isEmpty = true),
|
||||
),
|
||||
viewState,
|
||||
|
@ -68,12 +70,12 @@ class CipherViewExtensionsTest {
|
|||
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Identity Content`() {
|
||||
val viewState = createCipherView(type = CipherType.IDENTITY, isEmpty = false)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val cipherView = createCipherView(type = CipherType.IDENTITY, isEmpty = false)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false),
|
||||
common = createCommonContent(isEmpty = false).copy(currentCipher = cipherView),
|
||||
type = createIdentityContent(isEmpty = false),
|
||||
),
|
||||
viewState,
|
||||
|
@ -82,12 +84,12 @@ class CipherViewExtensionsTest {
|
|||
|
||||
@Test
|
||||
fun `toViewState should transform empty CipherView into ViewState Identity Content`() {
|
||||
val viewState = createCipherView(type = CipherType.IDENTITY, isEmpty = true)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val cipherView = createCipherView(type = CipherType.IDENTITY, isEmpty = true)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = true),
|
||||
common = createCommonContent(isEmpty = true).copy(currentCipher = cipherView),
|
||||
type = createIdentityContent(isEmpty = true),
|
||||
),
|
||||
viewState,
|
||||
|
@ -97,51 +99,62 @@ class CipherViewExtensionsTest {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should transform CipherView with odd naming into ViewState Identity Content`() {
|
||||
val viewState = createCipherView(type = CipherType.IDENTITY, isEmpty = false)
|
||||
val result = viewState
|
||||
val initialCipherView = createCipherView(type = CipherType.IDENTITY, isEmpty = false)
|
||||
val cipherView = initialCipherView
|
||||
.copy(
|
||||
identity = viewState.identity?.copy(
|
||||
identity = initialCipherView.identity?.copy(
|
||||
title = "MX",
|
||||
firstName = null,
|
||||
middleName = "middleName",
|
||||
lastName = null,
|
||||
),
|
||||
)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false),
|
||||
common = createCommonContent(isEmpty = false).copy(currentCipher = cipherView),
|
||||
type = createIdentityContent(
|
||||
isEmpty = false,
|
||||
identityName = "Mx middleName",
|
||||
),
|
||||
),
|
||||
result,
|
||||
viewState,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should transform CipherView with odd address into ViewState Identity Content`() {
|
||||
val viewState = createCipherView(type = CipherType.IDENTITY, isEmpty = false)
|
||||
val result = viewState
|
||||
.copy(
|
||||
identity = viewState.identity?.copy(
|
||||
address1 = null,
|
||||
address2 = null,
|
||||
address3 = "address3",
|
||||
city = null,
|
||||
state = "state",
|
||||
postalCode = null,
|
||||
country = null,
|
||||
),
|
||||
)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val initialCipherView = createCipherView(type = CipherType.IDENTITY, isEmpty = false)
|
||||
val cipherView = initialCipherView.copy(
|
||||
identity = initialCipherView.identity?.copy(
|
||||
address1 = null,
|
||||
address2 = null,
|
||||
address3 = "address3",
|
||||
city = null,
|
||||
state = "state",
|
||||
postalCode = null,
|
||||
country = null,
|
||||
),
|
||||
)
|
||||
val result = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false),
|
||||
common = createCommonContent(isEmpty = false).copy(
|
||||
currentCipher = cipherView.copy(
|
||||
identity = cipherView.identity?.copy(
|
||||
address1 = null,
|
||||
address2 = null,
|
||||
address3 = "address3",
|
||||
city = null,
|
||||
state = "state",
|
||||
postalCode = null,
|
||||
country = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
type = createIdentityContent(
|
||||
isEmpty = false,
|
||||
address = """
|
||||
|
@ -156,12 +169,12 @@ class CipherViewExtensionsTest {
|
|||
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Secure Note Content`() {
|
||||
val viewState = createCipherView(type = CipherType.SECURE_NOTE, isEmpty = false)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val cipherView = createCipherView(type = CipherType.SECURE_NOTE, isEmpty = false)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false),
|
||||
common = createCommonContent(isEmpty = false).copy(currentCipher = cipherView),
|
||||
type = VaultItemState.ViewState.Content.ItemType.SecureNote,
|
||||
),
|
||||
viewState,
|
||||
|
@ -171,11 +184,11 @@ class CipherViewExtensionsTest {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should transform empty Secure Note CipherView into ViewState Secure Note Content`() {
|
||||
val viewState = createCipherView(type = CipherType.SECURE_NOTE, isEmpty = true)
|
||||
.toViewState(isPremiumUser = true)
|
||||
val cipherView = createCipherView(type = CipherType.SECURE_NOTE, isEmpty = true)
|
||||
val viewState = cipherView.toViewState(isPremiumUser = true)
|
||||
|
||||
val expectedState = VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = true),
|
||||
common = createCommonContent(isEmpty = true).copy(currentCipher = cipherView),
|
||||
type = VaultItemState.ViewState.Content.ItemType.SecureNote,
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue