mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 20:09:59 +03:00
BIT-2398 if the org associated with a cipher uses TOTP enable the aut… (#3398)
This commit is contained in:
parent
9e0e07967f
commit
f13679cd2c
16 changed files with 314 additions and 11 deletions
|
@ -107,10 +107,11 @@ class AutofillCompletionManagerImpl(
|
|||
cipherView: CipherView,
|
||||
) {
|
||||
val isPremium = authRepository.userStateFlow.value?.activeAccount?.isPremium == true
|
||||
val totpAvailableViaPremiumOrOrganization = isPremium || cipherView.organizationUseTotp
|
||||
val totpCode = cipherView.login?.totp
|
||||
val isTotpDisabled = settingsRepository.isAutoCopyTotpDisabled
|
||||
|
||||
if (!isTotpDisabled && isPremium && totpCode != null) {
|
||||
if (!isTotpDisabled && totpAvailableViaPremiumOrOrganization && totpCode != null) {
|
||||
val totpResult = vaultRepository.generateTotp(
|
||||
time = DateTime.now(),
|
||||
totpCode = totpCode,
|
||||
|
|
|
@ -121,6 +121,7 @@ class TotpCodeManagerImpl(
|
|||
CipherRepromptType.PASSWORD -> true
|
||||
CipherRepromptType.NONE -> false
|
||||
},
|
||||
orgUsesTotp = cipher.organizationUseTotp,
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.bitwarden.vault.LoginUriView
|
|||
* @property name The name of the cipher item.
|
||||
* @property username The username associated with the item.
|
||||
* @property hasPasswordReprompt Indicates whether this item has a master password reprompt.
|
||||
* @property orgUsesTotp if the org providing the cipher uses TOTP.
|
||||
*/
|
||||
data class VerificationCodeItem(
|
||||
val code: String,
|
||||
|
@ -27,4 +28,5 @@ data class VerificationCodeItem(
|
|||
val name: String,
|
||||
val username: String?,
|
||||
val hasPasswordReprompt: Boolean,
|
||||
val orgUsesTotp: Boolean,
|
||||
)
|
||||
|
|
|
@ -117,7 +117,7 @@ fun VaultItemLoginContent(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
TotpField(
|
||||
totpCodeItemData = totpCodeItemData,
|
||||
isPremiumUser = loginItemState.isPremiumUser,
|
||||
enabled = loginItemState.canViewTotpCode,
|
||||
onCopyTotpClick = vaultLoginItemTypeHandlers.onCopyTotpCodeClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -366,11 +366,11 @@ private fun PasswordHistoryCount(
|
|||
@Composable
|
||||
private fun TotpField(
|
||||
totpCodeItemData: TotpCodeItemData,
|
||||
isPremiumUser: Boolean,
|
||||
enabled: Boolean,
|
||||
onCopyTotpClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (isPremiumUser) {
|
||||
if (enabled) {
|
||||
Row {
|
||||
BitwardenTextFieldWithActions(
|
||||
label = stringResource(id = R.string.verification_code_totp),
|
||||
|
|
|
@ -1275,8 +1275,16 @@ data class VaultItemState(
|
|||
* @property totpCodeItemData The optional data related the TOTP code.
|
||||
* @property isPremiumUser Indicates if the user has subscribed to a premium
|
||||
* account.
|
||||
* @property canViewTotpCode Indicates if the user can view an associated TOTP code.
|
||||
* @property fido2CredentialCreationDateText Optional creation date and time of the
|
||||
* FIDO2 credential associated with the login item.
|
||||
*
|
||||
* **NOTE** [canViewTotpCode] currently supports a deprecated edge case where an
|
||||
* organization supports TOTP but not through the current premium model.
|
||||
* This additional field is added to allow for [isPremiumUser] to be an independent
|
||||
* value.
|
||||
* @see [CipherView.organizationUseTotp]
|
||||
*
|
||||
*/
|
||||
@Parcelize
|
||||
data class Login(
|
||||
|
@ -1287,6 +1295,7 @@ data class VaultItemState(
|
|||
val passwordRevisionDate: String?,
|
||||
val totpCodeItemData: TotpCodeItemData?,
|
||||
val isPremiumUser: Boolean,
|
||||
val canViewTotpCode: Boolean,
|
||||
val fido2CredentialCreationDateText: Text?,
|
||||
) : ItemType() {
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ fun CipherView.toViewState(
|
|||
),
|
||||
passwordHistoryCount = passwordHistory?.count(),
|
||||
isPremiumUser = isPremiumUser,
|
||||
canViewTotpCode = isPremiumUser || this.organizationUseTotp,
|
||||
totpCodeItemData = totpCodeItemData,
|
||||
fido2CredentialCreationDateText = loginValues
|
||||
.fido2Credentials
|
||||
|
|
|
@ -64,11 +64,12 @@ fun VaultData.toViewState(
|
|||
return if (filteredCipherViewList.isEmpty()) {
|
||||
VaultState.ViewState.NoItems
|
||||
} else {
|
||||
val totpItems = filteredCipherViewList.filter { it.login?.totp != null }
|
||||
VaultState.ViewState.Content(
|
||||
totpItemsCount = if (isPremium) {
|
||||
filteredCipherViewList.count { it.login?.totp != null }
|
||||
totpItems.count()
|
||||
} else {
|
||||
0
|
||||
totpItems.count { it.organizationUseTotp }
|
||||
},
|
||||
loginItemsCount = filteredCipherViewList.count { it.type == CipherType.LOGIN },
|
||||
cardItemsCount = filteredCipherViewList.count { it.type == CipherType.CARD },
|
||||
|
|
|
@ -3,6 +3,8 @@ package com.x8bit.bitwarden.ui.vault.feature.verificationcode
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
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.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -18,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
|||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -32,6 +35,7 @@ import javax.inject.Inject
|
|||
@Suppress("TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class VerificationCodeViewModel @Inject constructor(
|
||||
authRepository: AuthRepository,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
|
@ -64,6 +68,13 @@ class VerificationCodeViewModel @Inject constructor(
|
|||
|
||||
vaultRepository
|
||||
.getAuthCodesFlow()
|
||||
.combine(authRepository.userStateFlow) { listDataState, userState ->
|
||||
if (listDataState is DataState.Loaded) {
|
||||
filterAuthCodesForDataState(listDataState.data, userState?.activeAccount)
|
||||
} else {
|
||||
listDataState
|
||||
}
|
||||
}
|
||||
.onEach {
|
||||
sendAction(
|
||||
VerificationCodeAction.Internal.AuthCodesReceive(it),
|
||||
|
@ -294,7 +305,23 @@ class VerificationCodeViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
//endregion VerificationCode Handlers
|
||||
/**
|
||||
* Filter verification codes in the event that the user is not a "premium" account but
|
||||
* has TOTP codes associated with a legacy organization.
|
||||
*/
|
||||
private fun filterAuthCodesForDataState(
|
||||
authCodes: List<VerificationCodeItem>,
|
||||
userAccount: UserState.Account?,
|
||||
): DataState<List<VerificationCodeItem>> {
|
||||
val filteredAuthCodes = authCodes.mapNotNull { authCode ->
|
||||
if (userAccount?.isPremium == true) {
|
||||
authCode
|
||||
} else {
|
||||
authCode.takeIf { it.orgUsesTotp }
|
||||
}
|
||||
}
|
||||
return DataState.Loaded(filteredAuthCodes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -224,7 +224,7 @@ class AutofillCompletionManagerTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `completeAutofill when filled partition, premium active user, a totp code, and totp generated succesfully should build a dataset, place it in a result Intent, copy totp, and finish the Activity`() {
|
||||
fun `completeAutofill when filled partition, premium active user, a totp code, and totp generated successfully should build a dataset, place it in a result Intent, copy totp, and finish the Activity`() {
|
||||
val filledData: FilledData = mockk {
|
||||
every { filledPartitions } returns listOf(filledPartition)
|
||||
}
|
||||
|
@ -277,8 +277,94 @@ class AutofillCompletionManagerTest {
|
|||
verify {
|
||||
activity.setResult(Activity.RESULT_OK, resultIntent)
|
||||
activity.finish()
|
||||
activity.intent
|
||||
clipboardManager.setText(any<String>())
|
||||
mockIntent.getAutofillAssistStructureOrNull()
|
||||
autofillParser.parse(
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
assistStructure = assistStructure,
|
||||
)
|
||||
filledPartition.buildDataset(
|
||||
authIntentSender = null,
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
)
|
||||
settingsRepository.isAutoCopyTotpDisabled
|
||||
createAutofillSelectionResultIntent(dataset = dataset)
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.verification_code_totp,
|
||||
Toast.LENGTH_LONG,
|
||||
)
|
||||
toast.show()
|
||||
organizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.CipherClientAutoFilled(cipherId = "cipherId"),
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
filledDataBuilder.build(autofillRequest = fillableRequest)
|
||||
vaultRepository.generateTotp(
|
||||
time = any(),
|
||||
totpCode = TOTP_CODE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `completeAutofill when filled partition, organization uses totp, a totp code, and totp generated successfully should build a dataset, place it in a result Intent, copy totp, and finish the Activity`() {
|
||||
val filledData: FilledData = mockk {
|
||||
every { filledPartitions } returns listOf(filledPartition)
|
||||
}
|
||||
val generateTotpResult = GenerateTotpResult.Success(
|
||||
code = TOTP_RESULT_VALUE,
|
||||
periodSeconds = 100,
|
||||
)
|
||||
every { activity.intent } returns mockIntent
|
||||
every { mockIntent.getAutofillAssistStructureOrNull() } returns assistStructure
|
||||
every {
|
||||
autofillParser.parse(
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
assistStructure = assistStructure,
|
||||
)
|
||||
} returns fillableRequest
|
||||
every { cipherView.login?.totp } returns TOTP_CODE
|
||||
every { cipherView.organizationUseTotp } returns true
|
||||
mutableUserStateFlow.value = mockk {
|
||||
every { activeAccount.isPremium } returns false
|
||||
}
|
||||
coEvery {
|
||||
filledDataBuilder.build(autofillRequest = fillableRequest)
|
||||
} returns filledData
|
||||
every {
|
||||
filledPartition.buildDataset(
|
||||
authIntentSender = null,
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
)
|
||||
} returns dataset
|
||||
every { settingsRepository.isAutoCopyTotpDisabled } returns false
|
||||
every { createAutofillSelectionResultIntent(dataset = dataset) } returns resultIntent
|
||||
coEvery {
|
||||
vaultRepository.generateTotp(
|
||||
time = any(),
|
||||
totpCode = TOTP_CODE,
|
||||
)
|
||||
} returns generateTotpResult
|
||||
every {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.verification_code_totp,
|
||||
Toast.LENGTH_LONG,
|
||||
)
|
||||
} returns toast
|
||||
|
||||
autofillCompletionManager.completeAutofill(
|
||||
activity = activity,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
|
||||
verify {
|
||||
activity.setResult(Activity.RESULT_OK, resultIntent)
|
||||
activity.finish()
|
||||
activity.intent
|
||||
clipboardManager.setText(any<String>())
|
||||
mockIntent.getAutofillAssistStructureOrNull()
|
||||
|
@ -446,7 +532,7 @@ class AutofillCompletionManagerTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `completeAutofill when filled partition, no premium active user, and totp code should build a dataset, place it in a result Intent, and finish the Activity`() {
|
||||
fun `completeAutofill when filled partition, no premium active user, organization does not use totp, and totp code should build a dataset, place it in a result Intent, and finish the Activity`() {
|
||||
val filledData: FilledData = mockk {
|
||||
every { filledPartitions } returns listOf(filledPartition)
|
||||
}
|
||||
|
@ -459,6 +545,7 @@ class AutofillCompletionManagerTest {
|
|||
)
|
||||
} returns fillableRequest
|
||||
every { cipherView.login?.totp } returns TOTP_CODE
|
||||
every { cipherView.organizationUseTotp } returns false
|
||||
coEvery {
|
||||
filledDataBuilder.build(autofillRequest = fillableRequest)
|
||||
} returns filledData
|
||||
|
@ -494,6 +581,7 @@ class AutofillCompletionManagerTest {
|
|||
authIntentSender = null,
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
)
|
||||
cipherView.organizationUseTotp
|
||||
settingsRepository.isAutoCopyTotpDisabled
|
||||
createAutofillSelectionResultIntent(dataset = dataset)
|
||||
organizationEventManager.trackEvent(
|
||||
|
|
|
@ -1214,6 +1214,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
uris = emptyList(),
|
||||
passwordRevisionDate = null,
|
||||
isPremiumUser = true,
|
||||
canViewTotpCode = true,
|
||||
totpCodeItemData = null,
|
||||
fido2CredentialCreationDateText = null,
|
||||
),
|
||||
|
@ -1416,7 +1417,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
currentState.copy(
|
||||
viewState = DEFAULT_LOGIN_VIEW_STATE.copy(
|
||||
type = DEFAULT_LOGIN.copy(
|
||||
isPremiumUser = false,
|
||||
canViewTotpCode = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -2258,6 +2259,7 @@ private val DEFAULT_LOGIN: VaultItemState.ViewState.Content.ItemType.Login =
|
|||
totpCode = "testCode",
|
||||
),
|
||||
fido2CredentialCreationDateText = null,
|
||||
canViewTotpCode = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_IDENTITY: VaultItemState.ViewState.Content.ItemType.Identity =
|
||||
|
@ -2308,6 +2310,7 @@ private val EMPTY_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
|
|||
passwordRevisionDate = null,
|
||||
totpCodeItemData = null,
|
||||
isPremiumUser = true,
|
||||
canViewTotpCode = true,
|
||||
fido2CredentialCreationDateText = null,
|
||||
)
|
||||
|
||||
|
|
|
@ -2539,6 +2539,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
periodSeconds = 30,
|
||||
),
|
||||
fido2CredentialCreationDateText = null,
|
||||
canViewTotpCode = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_CARD_TYPE: VaultItemState.ViewState.Content.ItemType.Card =
|
||||
|
|
|
@ -142,7 +142,43 @@ class CipherViewExtensionsTest {
|
|||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false, isPremiumUser = isPremiumUser)
|
||||
.copy(currentCipher = cipherView),
|
||||
type = createLoginContent(isEmpty = false).copy(isPremiumUser = isPremiumUser),
|
||||
type = createLoginContent(isEmpty = false).copy(
|
||||
isPremiumUser = isPremiumUser,
|
||||
canViewTotpCode = false
|
||||
),
|
||||
),
|
||||
viewState,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState should transform full CipherView into ViewState Login Content without premium but with org totp access`() {
|
||||
val isPremiumUser = false
|
||||
val cipherView = createCipherView(
|
||||
type = CipherType.LOGIN,
|
||||
isEmpty = false
|
||||
).copy(organizationUseTotp = true)
|
||||
val viewState = cipherView.toViewState(
|
||||
previousState = null,
|
||||
isPremiumUser = isPremiumUser,
|
||||
hasMasterPassword = true,
|
||||
totpCodeItemData = TotpCodeItemData(
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 15,
|
||||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
clock = fixedClock,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
common = createCommonContent(isEmpty = false, isPremiumUser = isPremiumUser)
|
||||
.copy(currentCipher = cipherView),
|
||||
type = createLoginContent(isEmpty = false).copy(
|
||||
isPremiumUser = isPremiumUser,
|
||||
canViewTotpCode = true
|
||||
),
|
||||
),
|
||||
viewState,
|
||||
)
|
||||
|
|
|
@ -240,6 +240,7 @@ fun createLoginContent(isEmpty: Boolean): VaultItemState.ViewState.Content.ItemT
|
|||
fido2CredentialCreationDateText = R.string.created_xy
|
||||
.asText("10/27/23", "12:00 PM")
|
||||
.takeUnless { isEmpty },
|
||||
canViewTotpCode = true,
|
||||
)
|
||||
|
||||
fun createIdentityContent(
|
||||
|
|
|
@ -317,6 +317,79 @@ class VaultDataExtensionsTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should return 1 for totpItemsCount if user does not have premium and has at least 1 totp items with org TOTP true`() {
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1).copy(organizationUseTotp = true)),
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isPremium = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultState.ViewState.Content(
|
||||
loginItemsCount = 1,
|
||||
cardItemsCount = 0,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
folderItems = listOf(),
|
||||
collectionItems = listOf(),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
totpItemsCount = 1,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState should omit non org related totp codes when user does not have premium`() {
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = listOf(
|
||||
createMockCipherView(number = 1).copy(organizationUseTotp = true),
|
||||
createMockCipherView(number = 2),
|
||||
),
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isPremium = false,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
hasMasterPassword = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultState.ViewState.Content(
|
||||
loginItemsCount = 2,
|
||||
cardItemsCount = 0,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
folderItems = listOf(),
|
||||
collectionItems = listOf(),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
totpItemsCount = 1,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toLoginIconData should return a IconData Local type if isIconLoadingDisabled is true`() {
|
||||
|
|
|
@ -4,6 +4,8 @@ import android.net.Uri
|
|||
import app.cash.turbine.test
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
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.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -53,6 +55,20 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
every { environmentStateFlow } returns mockk()
|
||||
}
|
||||
|
||||
private val mockUserAccount: UserState.Account = mockk {
|
||||
every { isPremium } returns true
|
||||
}
|
||||
|
||||
private val mockUserState: UserState = mockk {
|
||||
every { activeAccount } returns mockUserAccount
|
||||
}
|
||||
|
||||
private val mutableUserStateFlow: MutableStateFlow<UserState> = MutableStateFlow(mockUserState)
|
||||
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
private val mutablePullToRefreshEnabledFlow = MutableStateFlow(false)
|
||||
private val mutableIsIconLoadingDisabledFlow = MutableStateFlow(false)
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
|
@ -389,6 +405,47 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AuthCodeState Loaded with non premium user and no org TOTP enabled should cause navigate back`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
every { mockUserAccount.isPremium } returns false
|
||||
val viewModel = createViewModel()
|
||||
mutableAuthCodeFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
data = listOf(
|
||||
createVerificationCodeItem(number = 1),
|
||||
createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(VerificationCodeEvent.NavigateBack, awaitItem())
|
||||
assertEquals(VerificationCodeEvent.DismissPullToRefresh, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AuthCodeState Loaded with non premium user and one org TOTP enabled should return correct state`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
every { mockUserAccount.isPremium } returns false
|
||||
val viewModel = createViewModel()
|
||||
mutableAuthCodeFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
data = listOf(
|
||||
createVerificationCodeItem(number = 1).copy(orgUsesTotp = true),
|
||||
createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val displayItems =
|
||||
(viewModel.stateFlow.value.viewState as? VerificationCodeState.ViewState.Content)?.verificationCodeDisplayItems
|
||||
assertEquals(1, displayItems?.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AuthCodeFlow Loading should update state to Loading`() = runTest {
|
||||
mutableAuthCodeFlow.tryEmit(value = DataState.Loading)
|
||||
|
@ -451,6 +508,7 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
vaultRepository = vaultRepository,
|
||||
environmentRepository = environmentRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
authRepository = authRepository,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
|
|
@ -15,4 +15,5 @@ fun createVerificationCodeItem(number: Int = 1) =
|
|||
uriLoginViewList = createMockLoginView(1).uris,
|
||||
username = "mockUsername-$number",
|
||||
hasPasswordReprompt = false,
|
||||
orgUsesTotp = false,
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue