mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 01:16:02 +03:00
PM-11464 Add onboarding status to user Account to allow for root navigation to onboarding flow. (#3878)
This commit is contained in:
parent
3ecf1382b2
commit
759e926588
50 changed files with 1074 additions and 81 deletions
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
|
@ -282,4 +283,20 @@ interface AuthDiskSource {
|
|||
* Stores the [accountTokens] for the given [userId].
|
||||
*/
|
||||
fun storeAccountTokens(userId: String, accountTokens: AccountTokensJson?)
|
||||
|
||||
/**
|
||||
* Gets the onboarding status for the given [userId].
|
||||
*/
|
||||
fun getOnboardingStatus(userId: String): OnboardingStatus?
|
||||
|
||||
/**
|
||||
* Stores the [onboardingStatus] for the given [userId].
|
||||
*/
|
||||
fun storeOnboardingStatus(userId: String, onboardingStatus: OnboardingStatus?)
|
||||
|
||||
/**
|
||||
* Emits updates that track [getOnboardingStatus]. This will replay the last known value,
|
||||
* if any exists.
|
||||
*/
|
||||
fun getOnboardingStatusFlow(userId: String): Flow<OnboardingStatus?>
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk
|
|||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
|
||||
|
@ -42,6 +43,7 @@ private const val POLICIES_KEY = "policies"
|
|||
private const val SHOULD_TRUST_DEVICE_KEY = "shouldTrustDevice"
|
||||
private const val TDE_LOGIN_COMPLETE = "tdeLoginComplete"
|
||||
private const val USES_KEY_CONNECTOR = "usesKeyConnector"
|
||||
private const val ONBOARDING_STATUS_KEY = "onboardingStatus"
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
|
@ -67,6 +69,8 @@ class AuthDiskSourceImpl(
|
|||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Policy>?>>()
|
||||
private val mutableAccountTokensFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<AccountTokensJson?>>()
|
||||
private val mutableOnboardingStatusFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<OnboardingStatus?>>()
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override var userState: UserStateJson?
|
||||
|
@ -405,6 +409,25 @@ class AuthDiskSourceImpl(
|
|||
getMutableAccountTokensFlow(userId = userId).tryEmit(accountTokens)
|
||||
}
|
||||
|
||||
override fun getOnboardingStatus(userId: String): OnboardingStatus? {
|
||||
return getString(key = ONBOARDING_STATUS_KEY.appendIdentifier(userId))?.let {
|
||||
json.decodeFromStringOrNull(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeOnboardingStatus(userId: String, onboardingStatus: OnboardingStatus?) {
|
||||
putString(
|
||||
key = ONBOARDING_STATUS_KEY.appendIdentifier(userId),
|
||||
value = onboardingStatus?.let { json.encodeToString(it) },
|
||||
)
|
||||
getMutableOnboardingStatusFlow(userId = userId).tryEmit(onboardingStatus)
|
||||
}
|
||||
|
||||
override fun getOnboardingStatusFlow(userId: String): Flow<OnboardingStatus?> {
|
||||
return getMutableOnboardingStatusFlow(userId = userId)
|
||||
.onSubscription { emit(getOnboardingStatus(userId = userId)) }
|
||||
}
|
||||
|
||||
private fun generateAndStoreUniqueAppId(): String =
|
||||
UUID
|
||||
.randomUUID()
|
||||
|
@ -413,6 +436,13 @@ class AuthDiskSourceImpl(
|
|||
putString(key = UNIQUE_APP_ID_KEY, value = it)
|
||||
}
|
||||
|
||||
private fun getMutableOnboardingStatusFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<OnboardingStatus?> =
|
||||
mutableOnboardingStatusFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableShouldUseKeyConnectorFlowMap(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> =
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Describes the current status of a user in the account onboarding steps.
|
||||
*/
|
||||
@Serializable
|
||||
enum class OnboardingStatus {
|
||||
|
||||
/**
|
||||
* Onboarding has not yet started.
|
||||
*/
|
||||
@SerialName("notStarted")
|
||||
NOT_STARTED,
|
||||
|
||||
/**
|
||||
* The user is completing the account lock setup.
|
||||
*/
|
||||
@SerialName("accountLockSetup")
|
||||
ACCOUNT_LOCK_SETUP,
|
||||
|
||||
/**
|
||||
* The user is completing the auto fill service setup.
|
||||
*/
|
||||
@SerialName("autofillSetup")
|
||||
AUTOFILL_SETUP,
|
||||
|
||||
/**
|
||||
* The user has completed all onboarding steps.
|
||||
*/
|
||||
@SerialName("complete")
|
||||
COMPLETE,
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
|
||||
|
@ -386,4 +387,9 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
|
|||
email: String,
|
||||
token: String,
|
||||
): EmailTokenResult
|
||||
|
||||
/**
|
||||
* Update the value of the onboarding status for the user.
|
||||
*/
|
||||
fun setOnboardingStatus(userId: String, status: OnboardingStatus?)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
|||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
|
||||
|
@ -74,6 +75,8 @@ import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.currentOnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.onboardingStatusChangesFlow
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toRemovedPasswordUserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||
|
@ -250,6 +253,7 @@ class AuthRepositoryImpl(
|
|||
authDiskSource.userAccountTokensFlow,
|
||||
authDiskSource.userOrganizationsListFlow,
|
||||
authDiskSource.userKeyConnectorStateFlow,
|
||||
authDiskSource.onboardingStatusChangesFlow,
|
||||
vaultRepository.vaultUnlockDataStateFlow,
|
||||
mutableHasPendingAccountAdditionStateFlow,
|
||||
// Ignore the data in the merge, but trigger an update when they emit.
|
||||
|
@ -262,14 +266,16 @@ class AuthRepositoryImpl(
|
|||
val userAccountTokens = array[1] as List<UserAccountTokens>
|
||||
val userOrganizationsList = array[2] as List<UserOrganizations>
|
||||
val userIsUsingKeyConnectorList = array[3] as List<UserKeyConnectorState>
|
||||
val vaultState = array[4] as List<VaultUnlockData>
|
||||
val hasPendingAccountAddition = array[5] as Boolean
|
||||
val onboardingStatus = array[4] as OnboardingStatus?
|
||||
val vaultState = array[5] as List<VaultUnlockData>
|
||||
val hasPendingAccountAddition = array[6] as Boolean
|
||||
userStateJson?.toUserState(
|
||||
vaultState = vaultState,
|
||||
userAccountTokens = userAccountTokens,
|
||||
userOrganizationsList = userOrganizationsList,
|
||||
userIsUsingKeyConnectorList = userIsUsingKeyConnectorList,
|
||||
hasPendingAccountAddition = hasPendingAccountAddition,
|
||||
onboardingStatus = onboardingStatus,
|
||||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isDeviceTrustedProvider = ::isDeviceTrusted,
|
||||
|
@ -288,6 +294,7 @@ class AuthRepositoryImpl(
|
|||
userOrganizationsList = authDiskSource.userOrganizationsList,
|
||||
userIsUsingKeyConnectorList = authDiskSource.userKeyConnectorStateList,
|
||||
hasPendingAccountAddition = mutableHasPendingAccountAdditionStateFlow.value,
|
||||
onboardingStatus = authDiskSource.currentOnboardingStatus,
|
||||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isDeviceTrustedProvider = ::isDeviceTrusted,
|
||||
|
@ -1284,6 +1291,10 @@ class AuthRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override fun setOnboardingStatus(userId: String, status: OnboardingStatus?) {
|
||||
authDiskSource.storeOnboardingStatus(userId = userId, onboardingStatus = status)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun validatePasswordAgainstPolicy(
|
||||
password: String,
|
||||
|
@ -1559,6 +1570,16 @@ class AuthRepositoryImpl(
|
|||
),
|
||||
)
|
||||
settingsRepository.hasUserLoggedInOrCreatedAccount = true
|
||||
|
||||
val shouldSetOnboardingStatus = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow) &&
|
||||
!settingsRepository.getUserHasLoggedInValue(userId = userId)
|
||||
if (shouldSetOnboardingStatus) {
|
||||
setOnboardingStatus(
|
||||
userId = userId,
|
||||
status = OnboardingStatus.NOT_STARTED,
|
||||
)
|
||||
}
|
||||
|
||||
authDiskSource.userState = userStateJson
|
||||
loginResponse.key?.let {
|
||||
// Only set the value if it's present, since we may have set it already
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState.Account
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
||||
|
@ -69,6 +70,7 @@ data class UserState(
|
|||
val isBiometricsEnabled: Boolean,
|
||||
val vaultUnlockType: VaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
val isUsingKeyConnector: Boolean,
|
||||
val onboardingStatus: OnboardingStatus,
|
||||
) {
|
||||
/**
|
||||
* Indicates that the user does or does not have a means to manually unlock the vault.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserAccountTokens
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserKeyConnectorState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||
|
@ -10,6 +11,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
|
@ -167,3 +169,22 @@ val AuthDiskSource.activeUserIdChangesFlow: Flow<String?>
|
|||
.userStateFlow
|
||||
.map { it?.activeUserId }
|
||||
.distinctUntilChanged()
|
||||
|
||||
/**
|
||||
* Returns a [Flow] that emits every time the active user's onboarding status is changed
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val AuthDiskSource.onboardingStatusChangesFlow: Flow<OnboardingStatus?>
|
||||
get() = activeUserIdChangesFlow
|
||||
.flatMapLatest { activeUserId ->
|
||||
activeUserId
|
||||
?.let { this.getOnboardingStatusFlow(userId = it) }
|
||||
?: flowOf(null)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
val AuthDiskSource.currentOnboardingStatus: OnboardingStatus?
|
||||
get() = this
|
||||
.userState
|
||||
?.activeUserId
|
||||
?.let { this.getOnboardingStatus(userId = it) }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserAccountTokens
|
||||
|
@ -109,6 +110,7 @@ fun UserStateJson.toUserState(
|
|||
userOrganizationsList: List<UserOrganizations>,
|
||||
userIsUsingKeyConnectorList: List<UserKeyConnectorState>,
|
||||
hasPendingAccountAddition: Boolean,
|
||||
onboardingStatus: OnboardingStatus?,
|
||||
isBiometricsEnabledProvider: (userId: String) -> Boolean,
|
||||
vaultUnlockTypeProvider: (userId: String) -> VaultUnlockType,
|
||||
isDeviceTrustedProvider: (userId: String) -> Boolean,
|
||||
|
@ -171,6 +173,9 @@ fun UserStateJson.toUserState(
|
|||
isUsingKeyConnector = userIsUsingKeyConnectorList
|
||||
.find { it.userId == userId }
|
||||
?.isUsingKeyConnector == true,
|
||||
// If the user exists with no onboarding status we can assume they have been
|
||||
// using the app prior to the release of the onboarding flow.
|
||||
onboardingStatus = onboardingStatus ?: OnboardingStatus.COMPLETE,
|
||||
)
|
||||
},
|
||||
hasPendingAccountAddition = hasPendingAccountAddition,
|
||||
|
|
|
@ -5,7 +5,10 @@ import androidx.navigation.NavGraphBuilder
|
|||
import androidx.navigation.NavOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
|
||||
private const val SETUP_AUTO_FILL_ROUTE = "setup_auto_fill"
|
||||
/**
|
||||
* Route name for [SetupAutoFillScreen].
|
||||
*/
|
||||
const val SETUP_AUTO_FILL_ROUTE = "setup_auto_fill"
|
||||
|
||||
/**
|
||||
* Navigate to the setup auto-fill screen.
|
||||
|
@ -17,14 +20,10 @@ fun NavController.navigateToSetupAutoFillScreen(navOptions: NavOptions? = null)
|
|||
/**
|
||||
* Add the setup auto-fil screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupAutoFillDestination(
|
||||
onNavigateToCompleteSetup: () -> Unit,
|
||||
) {
|
||||
fun NavGraphBuilder.setupAutoFillDestination() {
|
||||
composableWithPushTransitions(
|
||||
route = SETUP_AUTO_FILL_ROUTE,
|
||||
) {
|
||||
SetupAutoFillScreen(
|
||||
onNavigateToCompleteSetup = onNavigateToCompleteSetup,
|
||||
)
|
||||
SetupAutoFillScreen()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,11 +72,12 @@ class SetupAutoFillViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleTurnOnLaterConfirmClick() {
|
||||
sendEvent(SetupAutoFillEvent.NavigateToCompleteSetup)
|
||||
// TODO PM-10631 record user chose to turn on later for settings badging.
|
||||
// TODO PM-10632 update status to complete setup step.
|
||||
}
|
||||
|
||||
private fun handleContinueClick() {
|
||||
sendEvent(SetupAutoFillEvent.NavigateToCompleteSetup)
|
||||
// TODO PM-10632 update status to complete setup step.
|
||||
}
|
||||
|
||||
private fun handleAutofillServiceChanged(action: SetupAutoFillAction.AutofillServiceChanged) {
|
||||
|
@ -115,10 +116,6 @@ sealed class SetupAutoFillDialogState {
|
|||
* UI Events for the Auto-fill setup screen.
|
||||
*/
|
||||
sealed class SetupAutoFillEvent {
|
||||
/**
|
||||
* Navigate to the complete setup screen.
|
||||
*/
|
||||
data object NavigateToCompleteSetup : SetupAutoFillEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the autofill settings screen.
|
||||
|
|
|
@ -57,7 +57,6 @@ import com.x8bit.bitwarden.ui.platform.util.isPortrait
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SetupAutoFillScreen(
|
||||
onNavigateToCompleteSetup: () -> Unit,
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
viewModel: SetupAutoFillViewModel = hiltViewModel(),
|
||||
) {
|
||||
|
@ -65,7 +64,6 @@ fun SetupAutoFillScreen(
|
|||
val handler = rememberSetupAutoFillHandler(viewModel = viewModel)
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
SetupAutoFillEvent.NavigateToCompleteSetup -> onNavigateToCompleteSetup()
|
||||
SetupAutoFillEvent.NavigateToAutofillSettings -> {
|
||||
val showFallback = !intentManager.startSystemAutofillSettingsActivity()
|
||||
if (showFallback) {
|
||||
|
|
|
@ -5,7 +5,10 @@ import androidx.navigation.NavGraphBuilder
|
|||
import androidx.navigation.NavOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
|
||||
private const val SETUP_UNLOCK_ROUTE = "setup_unlock"
|
||||
/**
|
||||
* Route for [SetupUnlockScreen]
|
||||
*/
|
||||
const val SETUP_UNLOCK_ROUTE = "setup_unlock"
|
||||
|
||||
/**
|
||||
* Navigate to the setup unlock screen.
|
||||
|
@ -17,14 +20,10 @@ fun NavController.navigateToSetupUnlockScreen(navOptions: NavOptions? = null) {
|
|||
/**
|
||||
* Add the setup unlock screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupUnlockDestination(
|
||||
onNavigateToSetupAutofill: () -> Unit,
|
||||
) {
|
||||
fun NavGraphBuilder.setupUnlockDestination() {
|
||||
composableWithPushTransitions(
|
||||
route = SETUP_UNLOCK_ROUTE,
|
||||
) {
|
||||
SetupUnlockScreen(
|
||||
onNavigateToSetupAutofill = onNavigateToSetupAutofill,
|
||||
)
|
||||
SetupUnlockScreen()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,6 @@ import com.x8bit.bitwarden.ui.platform.util.isPortrait
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SetupUnlockScreen(
|
||||
onNavigateToSetupAutofill: () -> Unit,
|
||||
viewModel: SetupUnlockViewModel = hiltViewModel(),
|
||||
biometricsManager: BiometricsManager = LocalBiometricsManager.current,
|
||||
) {
|
||||
|
@ -71,7 +70,6 @@ fun SetupUnlockScreen(
|
|||
var showBiometricsPrompt by rememberSaveable { mutableStateOf(value = false) }
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
SetupUnlockEvent.NavigateToSetupAutofill -> onNavigateToSetupAutofill()
|
||||
is SetupUnlockEvent.ShowBiometricsPrompt -> {
|
||||
showBiometricsPrompt = true
|
||||
biometricsManager.promptBiometrics(
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -67,7 +68,7 @@ class SetupUnlockViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleContinueClick() {
|
||||
sendEvent(SetupUnlockEvent.NavigateToSetupAutofill)
|
||||
updateOnboardingStatusToNextStep()
|
||||
}
|
||||
|
||||
private fun handleEnableBiometricsClick() {
|
||||
|
@ -94,7 +95,7 @@ class SetupUnlockViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleSetUpLaterClick() {
|
||||
sendEvent(SetupUnlockEvent.NavigateToSetupAutofill)
|
||||
updateOnboardingStatusToNextStep()
|
||||
}
|
||||
|
||||
private fun handleDismissDialog() {
|
||||
|
@ -173,6 +174,10 @@ class SetupUnlockViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOnboardingStatusToNextStep() {
|
||||
authRepository.setOnboardingStatus(state.userId, OnboardingStatus.AUTOFILL_SETUP)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -219,10 +224,6 @@ data class SetupUnlockState(
|
|||
* Models events for the setup unlock screen.
|
||||
*/
|
||||
sealed class SetupUnlockEvent {
|
||||
/**
|
||||
* Navigate to autofill setup.
|
||||
*/
|
||||
data object NavigateToSetupAutofill : SetupUnlockEvent()
|
||||
|
||||
/**
|
||||
* Shows the prompt for biometrics using with the given [cipher].
|
||||
|
|
|
@ -15,6 +15,12 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navOptions
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_AUTO_FILL_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_UNLOCK_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupAutoFillScreen
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupUnlockScreen
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupAutoFillDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.auth.AUTH_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.auth.authGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.auth.navigateToAuthGraph
|
||||
|
@ -90,6 +96,8 @@ fun RootNavScreen(
|
|||
vaultUnlockDestination()
|
||||
vaultUnlockedGraph(navController)
|
||||
setupDebugMenuDestination(onNavigateBack = { navController.popBackStack() })
|
||||
setupUnlockDestination()
|
||||
setupAutoFillDestination()
|
||||
}
|
||||
|
||||
val targetRoute = when (state) {
|
||||
|
@ -114,6 +122,9 @@ fun RootNavScreen(
|
|||
is RootNavState.VaultUnlockedForFido2Assertion,
|
||||
is RootNavState.VaultUnlockedForFido2GetCredentials,
|
||||
-> VAULT_UNLOCKED_GRAPH_ROUTE
|
||||
|
||||
RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_ROUTE
|
||||
RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_ROUTE
|
||||
}
|
||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||
|
||||
|
@ -217,6 +228,14 @@ fun RootNavScreen(
|
|||
navOptions = rootNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
RootNavState.OnboardingAccountLockSetup -> {
|
||||
navController.navigateToSetupUnlockScreen(rootNavOptions)
|
||||
}
|
||||
|
||||
RootNavState.OnboardingAutoFillSetup -> {
|
||||
navController.navigateToSetupAutoFillScreen(rootNavOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
|||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -89,6 +90,17 @@ class RootNavViewModel @Inject constructor(
|
|||
RootNavState.RemovePassword
|
||||
}
|
||||
|
||||
userState.activeAccount.isVaultUnlocked &&
|
||||
userState.activeAccount.onboardingStatus != OnboardingStatus.COMPLETE -> {
|
||||
when (userState.activeAccount.onboardingStatus) {
|
||||
OnboardingStatus.NOT_STARTED,
|
||||
OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||
-> RootNavState.OnboardingAccountLockSetup
|
||||
OnboardingStatus.AUTOFILL_SETUP -> RootNavState.OnboardingAutoFillSetup
|
||||
OnboardingStatus.COMPLETE -> throw IllegalStateException("Should not have entered here.")
|
||||
}
|
||||
}
|
||||
|
||||
userState.activeAccount.isVaultUnlocked -> {
|
||||
when (specialCircumstance) {
|
||||
is SpecialCircumstance.AutofillSave -> {
|
||||
|
@ -320,6 +332,18 @@ sealed class RootNavState : Parcelable {
|
|||
*/
|
||||
@Parcelize
|
||||
data object ExpiredRegistrationLink : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the set up account lock onboarding screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object OnboardingAccountLockSetup : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the set up autofill onboarding screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object OnboardingAutoFillSetup : RootNavState()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.pm.SigningInfo
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
|
@ -1021,6 +1022,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
|||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
|
@ -1084,6 +1085,54 @@ class AuthDiskSourceTest {
|
|||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getOnboardingStatus should update SharedPreferences`() {
|
||||
val onboardingStatusBaseKey = "bwPreferencesStorage:onboardingStatus"
|
||||
val mockUserId = "mockUserId"
|
||||
val expectedStatus = OnboardingStatus.AUTOFILL_SETUP
|
||||
fakeSharedPreferences.edit {
|
||||
putString(
|
||||
"${onboardingStatusBaseKey}_$mockUserId",
|
||||
json.encodeToString(expectedStatus),
|
||||
)
|
||||
}
|
||||
val actual = authDiskSource.getOnboardingStatus(userId = mockUserId)
|
||||
assertEquals(
|
||||
expectedStatus,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeOnboardingStatus should update SharedPreferences`() {
|
||||
val onboardingStatusBaseKey = "bwPreferencesStorage:onboardingStatus"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockOnboardingStatus = OnboardingStatus.AUTOFILL_SETUP
|
||||
authDiskSource.storeOnboardingStatus(mockUserId, mockOnboardingStatus)
|
||||
|
||||
val actual = fakeSharedPreferences.getString(
|
||||
"${onboardingStatusBaseKey}_$mockUserId",
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
json.encodeToString(mockOnboardingStatus),
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getOnboardingStatusFlow should react to changes from storeOnboardingStatus`() = runTest {
|
||||
val userId = "userId"
|
||||
authDiskSource.getOnboardingStatusFlow(userId).test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(awaitItem())
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
authDiskSource.storeOnboardingStatus(userId, OnboardingStatus.AUTOFILL_SETUP)
|
||||
assertEquals(OnboardingStatus.AUTOFILL_SETUP, awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val USER_STATE_JSON = """
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.util
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
|
@ -26,6 +27,9 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Policy>?>>()
|
||||
private val mutableAccountTokensFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<AccountTokensJson?>>()
|
||||
|
||||
private val mutableOnboardingStatusFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<OnboardingStatus?>>()
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
private val storedShouldUseKeyConnector = mutableMapOf<String, Boolean?>()
|
||||
|
@ -48,6 +52,7 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
private val storedMasterPasswordHashes = mutableMapOf<String, String?>()
|
||||
private val storedAuthenticationSyncKeys = mutableMapOf<String, String?>()
|
||||
private val storedPolicies = mutableMapOf<String, List<SyncResponseJson.Policy>?>()
|
||||
private val storedOnboardingStatus = mutableMapOf<String, OnboardingStatus?>()
|
||||
|
||||
override var userState: UserStateJson? = null
|
||||
set(value) {
|
||||
|
@ -250,6 +255,18 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
getMutableAccountTokensFlow(userId = userId).tryEmit(accountTokens)
|
||||
}
|
||||
|
||||
override fun getOnboardingStatus(userId: String): OnboardingStatus? =
|
||||
storedOnboardingStatus[userId]
|
||||
|
||||
override fun storeOnboardingStatus(userId: String, onboardingStatus: OnboardingStatus?) {
|
||||
storedOnboardingStatus[userId] = onboardingStatus
|
||||
getMutableOnboardingStatusFlow(userId = userId).tryEmit(onboardingStatus)
|
||||
}
|
||||
|
||||
override fun getOnboardingStatusFlow(userId: String): Flow<OnboardingStatus?> =
|
||||
getMutableOnboardingStatusFlow(userId = userId)
|
||||
.onSubscription { emit(getOnboardingStatus(userId)) }
|
||||
|
||||
/**
|
||||
* Assert the the [isTdeLoginComplete] was stored successfully using the [userId].
|
||||
*/
|
||||
|
@ -423,5 +440,12 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableOnboardingStatusFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<OnboardingStatus?> =
|
||||
mutableOnboardingStatusFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
//endregion Private helper functions
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
|||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
|
@ -233,7 +234,10 @@ class AuthRepositoryTest {
|
|||
getActivePoliciesFlow(type = PolicyTypeJson.MASTER_PASSWORD)
|
||||
} returns mutableActivePolicyFlow
|
||||
}
|
||||
private val featureFlagManager: FeatureFlagManager = mockk()
|
||||
|
||||
private val featureFlagManager: FeatureFlagManager = mockk(relaxed = true) {
|
||||
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
||||
}
|
||||
|
||||
private val repository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
|
@ -336,6 +340,7 @@ class AuthRepositoryTest {
|
|||
userOrganizationsList = emptyList(),
|
||||
userIsUsingKeyConnectorList = emptyList(),
|
||||
hasPendingAccountAddition = false,
|
||||
onboardingStatus = null,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
|
@ -364,6 +369,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -380,6 +386,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -408,6 +415,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -636,6 +644,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
)
|
||||
val finalUserState = SINGLE_USER_STATE_2.toUserState(
|
||||
vaultState = VAULT_UNLOCK_DATA,
|
||||
|
@ -646,6 +655,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
)
|
||||
val kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams()
|
||||
coEvery {
|
||||
|
@ -5347,6 +5357,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
|
@ -5380,6 +5391,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
|
@ -5411,6 +5423,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
)
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
assertEquals(
|
||||
|
@ -6095,6 +6108,197 @@ class AuthRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setOnboardingStatus should save the onboarding status to disk`() {
|
||||
val userId = "userId"
|
||||
repository.setOnboardingStatus(userId = userId, status = OnboardingStatus.NOT_STARTED)
|
||||
assertEquals(OnboardingStatus.NOT_STARTED, fakeAuthDiskSource.getOnboardingStatus(userId))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on successful login a new user should have onboarding status set if feature flag is on and has not previously logged in`() =
|
||||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
every { featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow) } returns true
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns successResponse.asSuccess()
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = PASSWORD,
|
||||
userKey = successResponse.key!!,
|
||||
),
|
||||
privateKey = successResponse.privateKey!!,
|
||||
organizationKeys = null,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
coEvery { vaultRepository.syncIfNecessary() } just runs
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(
|
||||
previousUserState = null,
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
every { settingsRepository.getUserHasLoggedInValue(USER_ID_1) } returns false
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
)
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = "key",
|
||||
)
|
||||
fakeAuthDiskSource.assertMasterPasswordHash(
|
||||
userId = USER_ID_1,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
)
|
||||
assertEquals(
|
||||
OnboardingStatus.NOT_STARTED,
|
||||
fakeAuthDiskSource.getOnboardingStatus(USER_ID_1),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on successful login does not set onboarding status if feature flag is off`() =
|
||||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
every { featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns successResponse.asSuccess()
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = PASSWORD,
|
||||
userKey = successResponse.key!!,
|
||||
),
|
||||
privateKey = successResponse.privateKey!!,
|
||||
organizationKeys = null,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
coEvery { vaultRepository.syncIfNecessary() } just runs
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(
|
||||
previousUserState = null,
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
)
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = "key",
|
||||
)
|
||||
fakeAuthDiskSource.assertMasterPasswordHash(
|
||||
userId = USER_ID_1,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
)
|
||||
verify { settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1) }
|
||||
assertNull(fakeAuthDiskSource.getOnboardingStatus(USER_ID_1))
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on successful login does not set onboarding status if feature flag is on but user has previously logged in`() =
|
||||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
every { featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow) } returns true
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns successResponse.asSuccess()
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
initUserCryptoMethod = InitUserCryptoMethod.Password(
|
||||
password = PASSWORD,
|
||||
userKey = successResponse.key!!,
|
||||
),
|
||||
privateKey = successResponse.privateKey!!,
|
||||
organizationKeys = null,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
coEvery { vaultRepository.syncIfNecessary() } just runs
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(
|
||||
previousUserState = null,
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
every { settingsRepository.getUserHasLoggedInValue(USER_ID_1) } returns true
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
)
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = "key",
|
||||
)
|
||||
fakeAuthDiskSource.assertMasterPasswordHash(
|
||||
userId = USER_ID_1,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
)
|
||||
verify { settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1) }
|
||||
assertNull(fakeAuthDiskSource.getOnboardingStatus(USER_ID_1))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNIQUE_APP_ID = "testUniqueAppId"
|
||||
private const val NAME = "Example Name"
|
||||
|
|
|
@ -4,6 +4,7 @@ import app.cash.turbine.test
|
|||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
|
@ -472,6 +473,39 @@ class AuthDiskSourceExtensionsTest {
|
|||
assertNull(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `userStateChangesFlow should emit changes when user state changes`() = runTest {
|
||||
authDiskSource.storeOnboardingStatus(MOCK_USER_ID, OnboardingStatus.NOT_STARTED)
|
||||
authDiskSource.onboardingStatusChangesFlow.test {
|
||||
assertNull(awaitItem())
|
||||
authDiskSource.userState = MOCK_USER_STATE
|
||||
assertEquals(
|
||||
|
||||
OnboardingStatus.NOT_STARTED,
|
||||
awaitItem(),
|
||||
)
|
||||
authDiskSource.userState = MOCK_USER_STATE.copy(
|
||||
accounts = mapOf(
|
||||
MOCK_USER_ID to MOCK_ACCOUNT,
|
||||
"mockId-2" to mockk(),
|
||||
),
|
||||
)
|
||||
expectNoEvents()
|
||||
authDiskSource.userState = null
|
||||
assertNull(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `currentOnboardingStatus should return the current onboarding status`() {
|
||||
authDiskSource.storeOnboardingStatus(MOCK_USER_ID, OnboardingStatus.COMPLETE)
|
||||
authDiskSource.userState = MOCK_USER_STATE
|
||||
assertEquals(
|
||||
OnboardingStatus.COMPLETE,
|
||||
authDiskSource.currentOnboardingStatus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val MOCK_USER_ID: String = "mockId-1"
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
|||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson
|
||||
|
@ -23,9 +24,12 @@ import io.mockk.mockk
|
|||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class UserStateJsonExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toUpdatedUserStateJn should do nothing for a non-matching account`() {
|
||||
fun `toUpdatedUserStateJson should do nothing for a non-matching account using toRemovedPasswordUserStateJson`() {
|
||||
val originalUserState = UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = mapOf("activeUserId" to mockk()),
|
||||
|
@ -38,7 +42,7 @@ class UserStateJsonExtensionsTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toUpdatedUserStateJn should create user decryption options without a password if not present`() {
|
||||
fun `toUpdatedUserStateJson should create user decryption options without a password if not present`() {
|
||||
val originalProfile = AccountJson.Profile(
|
||||
userId = "activeUserId",
|
||||
email = "email",
|
||||
|
@ -85,7 +89,7 @@ class UserStateJsonExtensionsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `toUpdatedUserStateJn should update user decryption options to not have a password`() {
|
||||
fun `toUpdatedUserStateJson should update user decryption options to not have a password`() {
|
||||
val originalProfile = AccountJson.Profile(
|
||||
userId = "activeUserId",
|
||||
email = "email",
|
||||
|
@ -353,6 +357,7 @@ class UserStateJsonExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -421,6 +426,7 @@ class UserStateJsonExtensionsTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -457,6 +463,7 @@ class UserStateJsonExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -521,6 +528,7 @@ class UserStateJsonExtensionsTest {
|
|||
isBiometricsEnabledProvider = { true },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -563,6 +571,7 @@ class UserStateJsonExtensionsTest {
|
|||
),
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -630,6 +639,229 @@ class UserStateJsonExtensionsTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toUserState should set the correct onboarding values result`() {
|
||||
assertEquals(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
// This value is calculated from the userId
|
||||
avatarColorHex = "#ffecbc49",
|
||||
environment = Environment.Eu,
|
||||
isPremium = true,
|
||||
isLoggedIn = false,
|
||||
isVaultUnlocked = false,
|
||||
needsPasswordReset = false,
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
shouldManageResetPassword = false,
|
||||
shouldUseKeyConnector = false,
|
||||
role = OrganizationType.ADMIN,
|
||||
),
|
||||
),
|
||||
isBiometricsEnabled = false,
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
needsMasterPassword = true,
|
||||
trustedDevice = UserState.TrustedDevice(
|
||||
isDeviceTrusted = true,
|
||||
hasAdminApproval = false,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasResetPasswordPermission = false,
|
||||
),
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
),
|
||||
UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = mapOf(
|
||||
"activeUserId" to AccountJson(
|
||||
profile = mockk {
|
||||
every { userId } returns "activeUserId"
|
||||
every { name } returns "activeName"
|
||||
every { email } returns "activeEmail"
|
||||
every { avatarColorHex } returns null
|
||||
every { hasPremium } returns true
|
||||
every { forcePasswordResetReason } returns null
|
||||
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
|
||||
hasMasterPassword = false,
|
||||
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
|
||||
encryptedPrivateKey = null,
|
||||
encryptedUserKey = null,
|
||||
hasAdminApproval = false,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasManageResetPasswordPermission = false,
|
||||
),
|
||||
keyConnectorUserDecryptionOptions = null,
|
||||
)
|
||||
},
|
||||
tokens = null,
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toUserState(
|
||||
vaultState = emptyList(),
|
||||
userAccountTokens = listOf(
|
||||
UserAccountTokens(
|
||||
userId = "activeUserId",
|
||||
accessToken = null,
|
||||
refreshToken = null,
|
||||
),
|
||||
),
|
||||
userOrganizationsList = listOf(
|
||||
UserOrganizations(
|
||||
userId = "activeUserId",
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
shouldManageResetPassword = false,
|
||||
shouldUseKeyConnector = false,
|
||||
role = OrganizationType.ADMIN,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
userIsUsingKeyConnectorList = listOf(
|
||||
UserKeyConnectorState(
|
||||
userId = "activeUserId",
|
||||
isUsingKeyConnector = true,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toUserState should set the default value of onboarding to COMPLETE when passed value is null`() {
|
||||
assertEquals(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
// This value is calculated from the userId
|
||||
avatarColorHex = "#ffecbc49",
|
||||
environment = Environment.Eu,
|
||||
isPremium = true,
|
||||
isLoggedIn = false,
|
||||
isVaultUnlocked = false,
|
||||
needsPasswordReset = false,
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
shouldManageResetPassword = false,
|
||||
shouldUseKeyConnector = false,
|
||||
role = OrganizationType.ADMIN,
|
||||
),
|
||||
),
|
||||
isBiometricsEnabled = false,
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
needsMasterPassword = true,
|
||||
trustedDevice = UserState.TrustedDevice(
|
||||
isDeviceTrusted = true,
|
||||
hasAdminApproval = false,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasResetPasswordPermission = false,
|
||||
),
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
),
|
||||
UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = mapOf(
|
||||
"activeUserId" to AccountJson(
|
||||
profile = mockk {
|
||||
every { userId } returns "activeUserId"
|
||||
every { name } returns "activeName"
|
||||
every { email } returns "activeEmail"
|
||||
every { avatarColorHex } returns null
|
||||
every { hasPremium } returns true
|
||||
every { forcePasswordResetReason } returns null
|
||||
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
|
||||
hasMasterPassword = false,
|
||||
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
|
||||
encryptedPrivateKey = null,
|
||||
encryptedUserKey = null,
|
||||
hasAdminApproval = false,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasManageResetPasswordPermission = false,
|
||||
),
|
||||
keyConnectorUserDecryptionOptions = null,
|
||||
)
|
||||
},
|
||||
tokens = null,
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toUserState(
|
||||
vaultState = emptyList(),
|
||||
userAccountTokens = listOf(
|
||||
UserAccountTokens(
|
||||
userId = "activeUserId",
|
||||
accessToken = null,
|
||||
refreshToken = null,
|
||||
),
|
||||
),
|
||||
userOrganizationsList = listOf(
|
||||
UserOrganizations(
|
||||
userId = "activeUserId",
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
shouldManageResetPassword = false,
|
||||
shouldUseKeyConnector = false,
|
||||
role = OrganizationType.ADMIN,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
userIsUsingKeyConnectorList = listOf(
|
||||
UserKeyConnectorState(
|
||||
userId = "activeUserId",
|
||||
isUsingKeyConnector = true,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
|||
import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
|
@ -532,6 +533,7 @@ private fun createMockAccounts(number: Int): List<UserState.Account> {
|
|||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -60,15 +60,6 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||
verify { settingsRepository.disableAutofill() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleContinueClick sends NavigateToCompleteSetup event`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
||||
assertEquals(SetupAutoFillEvent.NavigateToCompleteSetup, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleTurnOnLater click sets dialogState to TurnOnLaterDialog`() {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -79,15 +70,6 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleTurnOnLaterConfirmClick sends NavigateToCompleteSetup event`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
|
||||
assertEquals(SetupAutoFillEvent.NavigateToCompleteSetup, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleDismissDialog sets dialogState to null`() {
|
||||
val viewModel = createViewModel()
|
||||
|
|
|
@ -17,13 +17,11 @@ import io.mockk.mockk
|
|||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class SetupAutofillScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateToCompleteSetupCalled = false
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<SetupAutoFillEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
|
||||
|
@ -38,7 +36,6 @@ class SetupAutofillScreenTest : BaseComposeTest() {
|
|||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
SetupAutoFillScreen(
|
||||
onNavigateToCompleteSetup = { onNavigateToCompleteSetupCalled = true },
|
||||
intentManager = intentManager,
|
||||
viewModel = viewModel,
|
||||
)
|
||||
|
@ -109,12 +106,6 @@ class SetupAutofillScreenTest : BaseComposeTest() {
|
|||
verify { viewModel.trySendAction(SetupAutoFillAction.AutoFillServiceFallback) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToCompleteSetup should call onNavigateToCompleteSetup`() {
|
||||
mutableEventFlow.tryEmit(SetupAutoFillEvent.NavigateToCompleteSetup)
|
||||
assertTrue(onNavigateToCompleteSetupCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Show autofill fallback dialog when dialog state is AutoFillFallbackDialog`() {
|
||||
mutableStateFlow.update {
|
||||
|
|
|
@ -26,7 +26,6 @@ import io.mockk.slot
|
|||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.robolectric.annotation.Config
|
||||
|
@ -34,8 +33,6 @@ import javax.crypto.Cipher
|
|||
|
||||
class SetupUnlockScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateToSetupAutofillCalled = false
|
||||
|
||||
private val captureBiometricsSuccess = slot<(cipher: Cipher?) -> Unit>()
|
||||
private val captureBiometricsCancel = slot<() -> Unit>()
|
||||
private val captureBiometricsLockOut = slot<() -> Unit>()
|
||||
|
@ -65,7 +62,6 @@ class SetupUnlockScreenTest : BaseComposeTest() {
|
|||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
SetupUnlockScreen(
|
||||
onNavigateToSetupAutofill = { onNavigateToSetupAutofillCalled = true },
|
||||
viewModel = viewModel,
|
||||
biometricsManager = biometricsManager,
|
||||
)
|
||||
|
@ -91,12 +87,6 @@ class SetupUnlockScreenTest : BaseComposeTest() {
|
|||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToSetupAutofill event should invoke the navigate to autofill lambda`() {
|
||||
mutableEventFlow.tryEmit(SetupUnlockEvent.NavigateToSetupAutofill)
|
||||
assertTrue(onNavigateToSetupAutofillCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on unlock with biometrics should be toggled on or off according to state`() {
|
||||
composeTestRule.onNodeWithText(text = "Unlock with Biometrics").assertIsOff()
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
|
@ -30,6 +31,7 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
|||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { setOnboardingStatus(userId = any(), status = any()) } just runs
|
||||
}
|
||||
private val settingsRepository = mockk<SettingsRepository> {
|
||||
every { isUnlockWithPinEnabled } returns false
|
||||
|
@ -50,20 +52,26 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `ContinueClick should emit NavigateToSetupAutofill`() = runTest {
|
||||
fun `ContinueClick should call setOnboardingStatus`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
||||
assertEquals(SetupUnlockEvent.NavigateToSetupAutofill, awaitItem())
|
||||
verify {
|
||||
authRepository.setOnboardingStatus(
|
||||
userId = DEFAULT_USER_ID,
|
||||
status = OnboardingStatus.AUTOFILL_SETUP,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SetUpLaterClick should emit NavigateToSetupAutofill`() = runTest {
|
||||
fun `SetUpLaterClick should call setOnboardingStatus`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SetupUnlockAction.SetUpLaterClick)
|
||||
assertEquals(SetupUnlockEvent.NavigateToSetupAutofill, awaitItem())
|
||||
verify {
|
||||
authRepository.setOnboardingStatus(
|
||||
userId = DEFAULT_USER_ID,
|
||||
status = OnboardingStatus.AUTOFILL_SETUP,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,6 +310,7 @@ private val DEFAULT_USER_STATE: UserState = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
|
@ -86,6 +87,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -222,6 +224,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
|
@ -277,6 +280,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
|
@ -336,6 +340,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
|
@ -511,6 +516,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
val userState = UserState(
|
||||
|
@ -545,6 +551,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
val userState = UserState(
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
|
@ -130,6 +131,7 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.removepassword
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RemovePasswordResult
|
||||
|
@ -165,6 +166,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.NewSsoUserResult
|
||||
|
@ -274,6 +275,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
trustedDevice = TRUSTED_DEVICE,
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -215,6 +216,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -253,6 +255,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1056,6 +1059,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -225,6 +225,16 @@ class RootNavScreenTest : BaseComposeTest() {
|
|||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to onboarding graph works as expected:
|
||||
rootNavStateFlow.value =
|
||||
RootNavState.OnboardingAccountLockSetup
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "setup_unlock",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
||||
|
||||
import android.content.pm.SigningInfo
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.JwtTokenDataJson
|
||||
|
@ -104,6 +105,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -137,6 +139,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -167,6 +170,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -207,6 +211,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -244,6 +249,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -281,6 +287,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -315,6 +322,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -362,6 +370,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -393,6 +402,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -432,6 +442,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -471,6 +482,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -517,6 +529,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -563,6 +576,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -604,6 +618,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -644,6 +659,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -719,6 +735,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -769,6 +786,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -827,6 +845,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -865,6 +884,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -898,6 +918,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -906,6 +927,216 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(RootNavState.VaultLocked, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of NOT_STARTED the nav state should be OnboardingAccountLockSetup`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.OnboardingAccountLockSetup,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of ACCOUNT_LOCK_SETUP the nav state should be OnboardingAccountLockSetup`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.OnboardingAccountLockSetup,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of AUTOFILL_SETUP the nav state should be OnboardingAutoFillSetup`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.OnboardingAutoFillSetup,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an locked vault and they have a OnboardingStatus of NOT_STARTED the nav state should be VaultLocked`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = false,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.VaultLocked,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of null the nav state should be VaultUnlocked`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.VaultUnlocked(activeUserId = "activeUserId"),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of COMPLETED the nav state should be VaultUnlocked`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.VaultUnlocked(activeUserId = "activeUserId"),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createViewModel(): RootNavViewModel =
|
||||
RootNavViewModel(
|
||||
authRepository = authRepository,
|
||||
|
|
|
@ -7,6 +7,7 @@ import app.cash.turbine.turbineScope
|
|||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.LoginUriView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
|
@ -1424,6 +1425,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
|
||||
|
@ -697,6 +698,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deletea
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -242,6 +243,7 @@ private val DEFAULT_USER_STATE: UserState = UserState(
|
|||
),
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginap
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequest
|
||||
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestUpdatesResult
|
||||
|
@ -370,6 +371,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.bitwarden.exporters.ExportFormat
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
|
@ -722,6 +723,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import app.cash.turbine.test
|
|||
import app.cash.turbine.turbineScope
|
||||
import com.bitwarden.generators.PasswordGeneratorRequest
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -2406,6 +2407,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.bitwarden.send.SendView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -1101,6 +1102,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.bitwarden.vault.CollectionView
|
|||
import com.bitwarden.vault.FolderView
|
||||
import com.bitwarden.vault.UriMatchType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
|
@ -3897,6 +3898,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = false,
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.bitwarden.vault.PasswordHistoryView
|
|||
import com.bitwarden.vault.SecureNoteType
|
||||
import com.bitwarden.vault.SecureNoteView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
|
@ -446,6 +447,7 @@ class CipherViewExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
|
@ -562,6 +563,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import app.cash.turbine.test
|
|||
import app.cash.turbine.turbineScope
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -2579,6 +2580,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ import app.cash.turbine.test
|
|||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -3806,6 +3807,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -5,6 +5,7 @@ import app.cash.turbine.test
|
|||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.CollectionView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -513,6 +514,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -128,6 +129,7 @@ private fun createMockUserState(hasOrganizations: Boolean = true): UserState =
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.vault
|
|||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
|
@ -196,6 +197,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -280,6 +282,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1529,6 +1532,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "lockedUserId",
|
||||
|
@ -1546,6 +1550,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -85,6 +86,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "lockedUserId",
|
||||
|
@ -110,6 +112,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "unlockedUserId",
|
||||
|
@ -139,6 +142,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "loggedOutUserId",
|
||||
|
@ -168,6 +172,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -212,6 +217,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
.toAccountSummary(isActive = true),
|
||||
)
|
||||
|
@ -254,6 +260,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
.toAccountSummary(isActive = false),
|
||||
)
|
||||
|
@ -300,6 +307,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -326,6 +334,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
.toVaultFilterData(isIndividualVaultDisabled = false),
|
||||
)
|
||||
|
@ -381,6 +390,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
.toVaultFilterData(
|
||||
isIndividualVaultDisabled = false,
|
||||
|
@ -437,6 +447,7 @@ class UserStateExtensionsTest {
|
|||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
)
|
||||
.toVaultFilterData(
|
||||
isIndividualVaultDisabled = true,
|
||||
|
|
Loading…
Reference in a new issue