mirror of
https://github.com/bitwarden/android.git
synced 2026-05-09 13:29:18 -05:00
Compare commits
7 Commits
release-no
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39e72a86e2 | ||
|
|
ac80d662d5 | ||
|
|
08d173e8e1 | ||
|
|
8310a03d73 | ||
|
|
cabd1865f0 | ||
|
|
0c0509f567 | ||
|
|
02d3c19e1c |
@@ -1388,7 +1388,9 @@ class AuthRepositoryImpl(
|
||||
* - Cannot have two-factor authentication enabled.
|
||||
*/
|
||||
private fun newDeviceNoticePreConditionsValid(): Boolean {
|
||||
if (environmentRepository.environment.type == Environment.Type.SELF_HOSTED) {
|
||||
val checkEnvironment = !featureFlagManager.getFeatureFlag(FlagKey.IgnoreEnvironmentCheck)
|
||||
val isSelfHosted = environmentRepository.environment.type == Environment.Type.SELF_HOSTED
|
||||
if (checkEnvironment && isSelfHosted) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ sealed class FlagKey<out T : Any> {
|
||||
AppReviewPrompt,
|
||||
NewDevicePermanentDismiss,
|
||||
NewDeviceTemporaryDismiss,
|
||||
IgnoreEnvironmentCheck,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -161,6 +162,15 @@ sealed class FlagKey<out T : Any> {
|
||||
override val isRemotelyConfigured: Boolean = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Data object holding the feature flag key to ignore an environment check.
|
||||
*/
|
||||
data object IgnoreEnvironmentCheck : FlagKey<Boolean>() {
|
||||
override val keyName: String = "ignore-environment-check"
|
||||
override val defaultValue: Boolean = false
|
||||
override val isRemotelyConfigured: Boolean = false
|
||||
}
|
||||
|
||||
//region Dummy keys for testing
|
||||
/**
|
||||
* Data object holding the key for a [Boolean] flag to be used in tests.
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.newdevicenotice
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -24,19 +25,24 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.ContinueClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.EmailAccessToggle
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.LearnMoreClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessEvent.NavigateToTwoFactorOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -47,12 +53,19 @@ fun NewDeviceNoticeEmailAccessScreen(
|
||||
onNavigateBackToVault: () -> Unit,
|
||||
onNavigateToTwoFactorOptions: () -> Unit,
|
||||
viewModel: NewDeviceNoticeEmailAccessViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
NavigateToTwoFactorOptions -> onNavigateToTwoFactorOptions()
|
||||
NewDeviceNoticeEmailAccessEvent.NavigateBackToVault -> onNavigateBackToVault()
|
||||
is NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore -> {
|
||||
intentManager.launchUri(
|
||||
"https://bitwarden.com/help/new-device-verification/"
|
||||
.toUri(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +79,7 @@ fun NewDeviceNoticeEmailAccessScreen(
|
||||
}
|
||||
},
|
||||
onContinueClick = { viewModel.trySendAction(ContinueClick) },
|
||||
onLearnMoreClick = { viewModel.trySendAction(LearnMoreClick) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,6 +90,7 @@ private fun NewDeviceNoticeEmailAccessContent(
|
||||
isEmailAccessEnabled: Boolean,
|
||||
onEmailAccessToggleChanged: (Boolean) -> Unit,
|
||||
onContinueClick: () -> Unit,
|
||||
onLearnMoreClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
@@ -86,7 +101,7 @@ private fun NewDeviceNoticeEmailAccessContent(
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(104.dp))
|
||||
HeaderContent()
|
||||
HeaderContent(onLearnMoreClick = onLearnMoreClick)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
MainContent(
|
||||
email = email,
|
||||
@@ -110,7 +125,9 @@ private fun NewDeviceNoticeEmailAccessContent(
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
@Composable
|
||||
private fun ColumnScope.HeaderContent() {
|
||||
private fun ColumnScope.HeaderContent(
|
||||
onLearnMoreClick: () -> Unit,
|
||||
) {
|
||||
Image(
|
||||
painter = rememberVectorPainter(id = R.drawable.warning),
|
||||
contentDescription = null,
|
||||
@@ -132,6 +149,13 @@ private fun ColumnScope.HeaderContent() {
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
BitwardenClickableText(
|
||||
label = stringResource(id = R.string.learn_more),
|
||||
onClick = onLearnMoreClick,
|
||||
style = BitwardenTheme.typography.labelLarge,
|
||||
innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
modifier = Modifier.testTag("LearnMoreLabel"),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,6 +207,7 @@ private fun NewDeviceNoticeEmailAccessScreen_preview() {
|
||||
isEmailAccessEnabled = true,
|
||||
onEmailAccessToggleChanged = {},
|
||||
onContinueClick = {},
|
||||
onLearnMoreClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,23 @@ package com.x8bit.bitwarden.ui.auth.feature.newdevicenotice
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL_PERMANENT
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.ContinueClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.EmailAccessToggle
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.LearnMoreClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessEvent.NavigateToTwoFactorOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.ZonedDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
@@ -26,6 +29,7 @@ private const val KEY_STATE = "state"
|
||||
@HiltViewModel
|
||||
class NewDeviceNoticeEmailAccessViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<
|
||||
@@ -39,10 +43,20 @@ class NewDeviceNoticeEmailAccessViewModel @Inject constructor(
|
||||
isEmailAccessEnabled = false,
|
||||
),
|
||||
) {
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
vaultRepository.syncForResult()
|
||||
if (!authRepository.checkUserNeedsNewDeviceTwoFactorNotice()) {
|
||||
sendEvent(NewDeviceNoticeEmailAccessEvent.NavigateBackToVault)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: NewDeviceNoticeEmailAccessAction) {
|
||||
when (action) {
|
||||
ContinueClick -> handleContinueClick()
|
||||
is EmailAccessToggle -> handleEmailAccessToggle(action)
|
||||
LearnMoreClick -> handleLearnMoreClick()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +72,7 @@ class NewDeviceNoticeEmailAccessViewModel @Inject constructor(
|
||||
authRepository.setNewDeviceNoticeState(
|
||||
NewDeviceNoticeState(
|
||||
displayStatus = displayStatus,
|
||||
lastSeenDate = ZonedDateTime.now(),
|
||||
lastSeenDate = null,
|
||||
),
|
||||
)
|
||||
sendEvent(NewDeviceNoticeEmailAccessEvent.NavigateBackToVault)
|
||||
@@ -72,6 +86,10 @@ class NewDeviceNoticeEmailAccessViewModel @Inject constructor(
|
||||
it.copy(isEmailAccessEnabled = action.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLearnMoreClick() {
|
||||
sendEvent(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +114,11 @@ sealed class NewDeviceNoticeEmailAccessEvent {
|
||||
* Navigates back.
|
||||
*/
|
||||
data object NavigateBackToVault : NewDeviceNoticeEmailAccessEvent()
|
||||
|
||||
/**
|
||||
* Navigates to learn more about New Device Login Protection
|
||||
*/
|
||||
data object NavigateToLearnMore : NewDeviceNoticeEmailAccessEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,4 +134,9 @@ sealed class NewDeviceNoticeEmailAccessAction {
|
||||
* User tapped the email access toggle.
|
||||
*/
|
||||
data class EmailAccessToggle(val isEnabled: Boolean) : NewDeviceNoticeEmailAccessAction()
|
||||
|
||||
/**
|
||||
* User tapped the learn more button.
|
||||
*/
|
||||
data object LearnMoreClick : NewDeviceNoticeEmailAccessAction()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.newdevicenotice
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
|
||||
@@ -8,7 +9,9 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.ChangeAccountEmailClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.ContinueDialogClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.DismissDialogClick
|
||||
@@ -21,7 +24,10 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import java.time.ZonedDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -32,6 +38,9 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
|
||||
val authRepository: AuthRepository,
|
||||
val environmentRepository: EnvironmentRepository,
|
||||
val featureFlagManager: FeatureFlagManager,
|
||||
val settingsRepository: SettingsRepository,
|
||||
val vaultRepository: VaultRepository,
|
||||
private val clock: Clock,
|
||||
) : BaseViewModel<
|
||||
NewDeviceNoticeTwoFactorState,
|
||||
NewDeviceNoticeTwoFactorEvent,
|
||||
@@ -43,6 +52,15 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
|
||||
),
|
||||
),
|
||||
) {
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
vaultRepository.syncForResult()
|
||||
if (!authRepository.checkUserNeedsNewDeviceTwoFactorNotice()) {
|
||||
sendEvent(NewDeviceNoticeTwoFactorEvent.NavigateBackToVault)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val webTwoFactorUrl: String
|
||||
get() {
|
||||
val baseUrl = environmentRepository
|
||||
@@ -79,7 +97,7 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
|
||||
authRepository.setNewDeviceNoticeState(
|
||||
NewDeviceNoticeState(
|
||||
displayStatus = NewDeviceNoticeDisplayStatus.HAS_SEEN,
|
||||
lastSeenDate = null,
|
||||
lastSeenDate = ZonedDateTime.now(clock),
|
||||
),
|
||||
)
|
||||
sendEvent(NewDeviceNoticeTwoFactorEvent.NavigateBackToVault)
|
||||
@@ -88,6 +106,8 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
|
||||
private fun handleContinueDialog() {
|
||||
when (state.dialogState) {
|
||||
is ChangeAccountEmailDialog -> {
|
||||
// when the user leaves the app set sync date to null to force a sync on next unlock
|
||||
settingsRepository.vaultLastSync = null
|
||||
sendEvent(
|
||||
NewDeviceNoticeTwoFactorEvent.NavigateToChangeAccountEmail(url = webAccountUrl),
|
||||
)
|
||||
@@ -95,6 +115,8 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is TurnOnTwoFactorDialog -> {
|
||||
// when the user leaves the app set sync date to null to force a sync on next unlock
|
||||
settingsRepository.vaultLastSync = null
|
||||
sendEvent(
|
||||
NewDeviceNoticeTwoFactorEvent.NavigateToTurnOnTwoFactor(url = webTwoFactorUrl),
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
||||
FlagKey.CipherKeyEncryption,
|
||||
FlagKey.NewDevicePermanentDismiss,
|
||||
FlagKey.NewDeviceTemporaryDismiss,
|
||||
FlagKey.IgnoreEnvironmentCheck,
|
||||
-> BooleanFlagItem(
|
||||
label = flagKey.getDisplayLabel(),
|
||||
key = flagKey as FlagKey<Boolean>,
|
||||
@@ -85,4 +86,5 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
|
||||
FlagKey.CipherKeyEncryption -> stringResource(R.string.cipher_key_encryption)
|
||||
FlagKey.NewDevicePermanentDismiss -> stringResource(R.string.new_device_permanent_dismiss)
|
||||
FlagKey.NewDeviceTemporaryDismiss -> stringResource(R.string.new_device_temporary_dismiss)
|
||||
FlagKey.IgnoreEnvironmentCheck -> stringResource(R.string.ignore_environment_check)
|
||||
}
|
||||
|
||||
@@ -110,33 +110,20 @@ fun List<CollectionView>?.hasDeletePermissionInAtLeastOneCollection(
|
||||
/**
|
||||
* Checks if the user has permission to assign an item to a collection.
|
||||
*
|
||||
* Assigning to a collection is not allowed when the item is in a collection that the user does not
|
||||
* have "manage" permission for and is also in a collection they cannot view the passwords in.
|
||||
*
|
||||
* E.g., If an item is in A collection with "view except passwords" or "edit except passwords"
|
||||
* permission and in another with "manage" permission, the user **cannot** assign the item to other
|
||||
* collections. Conversely, if an item is in a collection with "manage" permission and another with
|
||||
* "view" or "edit" permission, the user **can** assign the item to other collections.
|
||||
* Assigning to a collection is only allowed when the item is in a collection that the user does
|
||||
* have "manage" or "edit" permission.
|
||||
*/
|
||||
fun List<CollectionView>?.canAssignToCollections(currentCollectionIds: List<String>?): Boolean {
|
||||
if (this.isNullOrEmpty()) return true
|
||||
if (currentCollectionIds.isNullOrEmpty()) return true
|
||||
|
||||
// Verify user can MANAGE at least one collection the item is in.
|
||||
// Verify user can MANAGE or EDIT at least one collection the item is in.
|
||||
return this
|
||||
.any {
|
||||
currentCollectionIds.contains(it.id) &&
|
||||
it.permission == CollectionPermission.MANAGE
|
||||
} &&
|
||||
|
||||
// Verify user does not have "edit except password" or "view except passwords"
|
||||
// permission in any collection the item is not in.
|
||||
this
|
||||
.none {
|
||||
currentCollectionIds.contains(it.id) &&
|
||||
(it.permission == CollectionPermission.EDIT_EXCEPT_PASSWORD ||
|
||||
it.permission == CollectionPermission.VIEW_EXCEPT_PASSWORDS)
|
||||
}
|
||||
(it.permission == CollectionPermission.MANAGE ||
|
||||
it.permission == CollectionPermission.EDIT)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1089,7 +1089,7 @@ Do you want to switch to this account?</string>
|
||||
<string name="copy_address">Copy address</string>
|
||||
<string name="important_notice">Important notice</string>
|
||||
<string name="bitwarden_will_soon_send_a_code_to_your_account_email_to_verify_logins_from_new_devices_in_february">Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025.</string>
|
||||
<string name="do_you_have_reliable_access_to_your_email">Do you have reliable access to your email, <annotation emphasis="bold"><annotation arg="0">%1$s?</annotation></annotation></string>
|
||||
<string name="do_you_have_reliable_access_to_your_email">Do you have reliable access to your email, <annotation emphasis="bold"><annotation arg="0">%1$s?</annotation></annotation>?</string>
|
||||
<string name="yes_i_can_reliably_access_my_email">Yes, I can reliably access my email</string>
|
||||
<string name="biometrics_no_longer_supported_title">Biometrics are no longer supported on this device</string>
|
||||
<string name="biometrics_no_longer_supported">You’ve been logged out because your device’s biometrics don’t meet the latest security requirements. To update settings, log in once again or contact your administrator for access.</string>
|
||||
|
||||
@@ -24,5 +24,6 @@
|
||||
<string name="cipher_key_encryption">Cipher Key Encryption</string>">
|
||||
<string name="new_device_permanent_dismiss">New device notice permanent dismiss</string>">
|
||||
<string name="new_device_temporary_dismiss">New device notice temporary dismiss</string>">
|
||||
<string name="ignore_environment_check">Ignore environment check</string>">
|
||||
<!-- /Debug Menu -->
|
||||
</resources>
|
||||
|
||||
@@ -250,7 +250,10 @@ class AuthRepositoryTest {
|
||||
}
|
||||
|
||||
private val featureFlagManager: FeatureFlagManager = mockk(relaxed = true) {
|
||||
every { getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss) } returns true
|
||||
every { getFeatureFlag(FlagKey.NewDevicePermanentDismiss) } returns true
|
||||
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
||||
every { getFeatureFlag(FlagKey.IgnoreEnvironmentCheck) } returns false
|
||||
}
|
||||
|
||||
private val firstTimeActionManager = mockk<FirstTimeActionManager> {
|
||||
@@ -6530,12 +6533,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice flags on, is cloud user, profile at least week old, no required sso policy, no two factor enable returns true`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6571,14 +6568,47 @@ class AuthRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice has required SSO policy returns false`() =
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice IgnoreEnvironmentCheck flag enabled should not check for a cloud environment and return true`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
featureFlagManager.getFeatureFlag(FlagKey.IgnoreEnvironmentCheck)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
fakeEnvironmentRepository.environment = Environment.SelfHosted(
|
||||
EnvironmentUrlDataJson(base = "https://myselfhosted.environment.com"),
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
|
||||
val shouldShowNewDeviceNotice = repository.checkUserNeedsNewDeviceTwoFactorNotice()
|
||||
|
||||
assertTrue(shouldShowNewDeviceNotice)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice if environment is selfhosted return false`() =
|
||||
runTest {
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
fakeEnvironmentRepository.environment = Environment.SelfHosted(
|
||||
EnvironmentUrlDataJson(base = "https://myselfhosted.environment.com"),
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
|
||||
val shouldShowNewDeviceNotice = repository.checkUserNeedsNewDeviceTwoFactorNotice()
|
||||
|
||||
assertFalse(shouldShowNewDeviceNotice)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice has required SSO policy returns false`() =
|
||||
runTest {
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf(
|
||||
@@ -6599,12 +6629,6 @@ class AuthRepositoryTest {
|
||||
@Test
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with two factor enable returns false`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6620,12 +6644,6 @@ class AuthRepositoryTest {
|
||||
@Test
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice account less than a week old returns false`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6652,12 +6670,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus CAN_ACCESS_EMAIL_PERMANENT return false`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6681,12 +6693,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus HAS_NOT_SEEN return true`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6710,12 +6716,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus HAS_SEEN return true if date is older than 7 days`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6739,12 +6739,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus HAS_SEEN return false if date is not older than 7 days`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6768,12 +6762,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus CAN_ACCESS_EMAIL return permanent flag value`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6800,12 +6788,6 @@ class AuthRepositoryTest {
|
||||
@Test
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice with no active user returns false`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6819,12 +6801,6 @@ class AuthRepositoryTest {
|
||||
@Test
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice account with null creationDate returns false`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
@@ -6840,7 +6816,6 @@ class AuthRepositoryTest {
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertFalse(repository.checkUserNeedsNewDeviceTwoFactorNotice())
|
||||
}
|
||||
|
||||
@@ -6848,12 +6823,6 @@ class AuthRepositoryTest {
|
||||
@Suppress("MaxLineLength")
|
||||
fun `checkUserNeedsNewDeviceTwoFactorNotice account with null isTwoFactorEnabled returns true`() =
|
||||
runTest {
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
|
||||
} returns true
|
||||
every {
|
||||
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
|
||||
} returns true
|
||||
every {
|
||||
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
|
||||
} returns listOf()
|
||||
|
||||
@@ -66,4 +66,14 @@ class FlagKeyTest {
|
||||
fun `NewDeviceTemporaryDismiss is remotely configured value should be true`() {
|
||||
assertTrue(FlagKey.NewDeviceTemporaryDismiss.isRemotelyConfigured)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `IgnoreEnvironmentCheck default value should be false`() {
|
||||
assertFalse(FlagKey.IgnoreEnvironmentCheck.defaultValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `IgnoreEnvironmentCheck is remotely configured value should be false`() {
|
||||
assertFalse(FlagKey.IgnoreEnvironmentCheck.isRemotelyConfigured)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,14 @@ import androidx.compose.ui.test.assertIsOn
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -27,6 +31,10 @@ class NewDeviceNoticeEmailAccessScreenTest : BaseComposeTest() {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
}
|
||||
|
||||
private val intentManager: IntentManager = mockk {
|
||||
every { launchUri(any()) } just runs
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
@@ -34,6 +42,7 @@ class NewDeviceNoticeEmailAccessScreenTest : BaseComposeTest() {
|
||||
onNavigateBackToVault = { onNavigateBackToVaultCalled = true },
|
||||
onNavigateToTwoFactorOptions = { onNavigateToTwoFactorOptionsCalled = true },
|
||||
viewModel = viewModel,
|
||||
intentManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -91,6 +100,14 @@ class NewDeviceNoticeEmailAccessScreenTest : BaseComposeTest() {
|
||||
mutableEventFlow.tryEmit(NewDeviceNoticeEmailAccessEvent.NavigateToTwoFactorOptions)
|
||||
assertTrue(onNavigateToTwoFactorOptionsCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToLearnMore should call launchUri on IntentManager`() {
|
||||
mutableEventFlow.tryEmit(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore)
|
||||
verify {
|
||||
intentManager.launchUri("https://bitwarden.com/help/new-device-verification/".toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val EMAIL = "active@bitwarden.com"
|
||||
|
||||
@@ -7,11 +7,13 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -31,6 +33,8 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
|
||||
every { getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss) } returns true
|
||||
}
|
||||
|
||||
private val vaultRepository = mockk<VaultRepository>(relaxed = true)
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct with email from state handle`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
@@ -39,6 +43,27 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Init should not send events if user needs new device notice`() = runTest {
|
||||
every { authRepository.checkUserNeedsNewDeviceTwoFactorNotice() } returns true
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Init should send NavigateBackToVault if user does not need new device notice`() = runTest {
|
||||
every { authRepository.checkUserNeedsNewDeviceTwoFactorNotice() } returns false
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
NewDeviceNoticeEmailAccessEvent.NavigateBackToVault,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `EmailAccessToggle should update value of isEmailAccessEnabled`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
@@ -61,6 +86,14 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
|
||||
NewDeviceNoticeEmailAccessEvent.NavigateBackToVault,
|
||||
awaitItem(),
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setNewDeviceNoticeState(
|
||||
NewDeviceNoticeState(
|
||||
displayStatus = NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL_PERMANENT,
|
||||
lastSeenDate = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +110,14 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
|
||||
NewDeviceNoticeEmailAccessEvent.NavigateBackToVault,
|
||||
awaitItem(),
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setNewDeviceNoticeState(
|
||||
NewDeviceNoticeState(
|
||||
displayStatus = NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL,
|
||||
lastSeenDate = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +134,16 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LearnMoreClick should emit NavigateToLearnMore`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(NewDeviceNoticeEmailAccessAction.LearnMoreClick)
|
||||
assertEquals(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
savedStateHandle: SavedStateHandle = SavedStateHandle().also {
|
||||
it["email_address"] = EMAIL
|
||||
@@ -100,6 +151,7 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
|
||||
): NewDeviceNoticeEmailAccessViewModel = NewDeviceNoticeEmailAccessViewModel(
|
||||
authRepository = authRepository,
|
||||
featureFlagManager = featureFlagManager,
|
||||
vaultRepository = vaultRepository,
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,26 +6,27 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorDialogState.ChangeAccountEmailDialog
|
||||
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorDialogState.TurnOnTwoFactorDialog
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
private val environmentRepository = FakeEnvironmentRepository()
|
||||
private val authRepository = mockk<AuthRepository> {
|
||||
every { getNewDeviceNoticeState() } returns NewDeviceNoticeState(
|
||||
displayStatus = NewDeviceNoticeDisplayStatus.HAS_NOT_SEEN,
|
||||
lastSeenDate = null,
|
||||
)
|
||||
every { setNewDeviceNoticeState(any()) } just runs
|
||||
private val authRepository = mockk<AuthRepository>(relaxed = true) {
|
||||
every { checkUserNeedsNewDeviceTwoFactorNotice() } returns true
|
||||
}
|
||||
|
||||
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
|
||||
@@ -33,6 +34,10 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
every { getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss) } returns true
|
||||
}
|
||||
|
||||
private val settingsRepository = mockk<SettingsRepository>(relaxed = true)
|
||||
|
||||
private val vaultRepository = mockk<VaultRepository>(relaxed = true)
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct with NewDevicePermanentDismiss flag false`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
@@ -53,6 +58,27 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Init should not send events if user needs new device notice`() = runTest {
|
||||
every { authRepository.checkUserNeedsNewDeviceTwoFactorNotice() } returns true
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Init should send NavigateBackToVault if user does not need new device notice`() = runTest {
|
||||
every { authRepository.checkUserNeedsNewDeviceTwoFactorNotice() } returns false
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
NewDeviceNoticeTwoFactorEvent.NavigateBackToVault,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct with email from state handle`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
@@ -109,6 +135,14 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
NewDeviceNoticeTwoFactorEvent.NavigateBackToVault,
|
||||
awaitItem(),
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setNewDeviceNoticeState(
|
||||
NewDeviceNoticeState(
|
||||
displayStatus = NewDeviceNoticeDisplayStatus.HAS_SEEN,
|
||||
lastSeenDate = ZonedDateTime.now(FIXED_CLOCK),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +164,9 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
DEFAULT_STATE,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
settingsRepository.vaultLastSync = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +188,9 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
DEFAULT_STATE,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
settingsRepository.vaultLastSync = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +212,9 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
|
||||
authRepository = authRepository,
|
||||
environmentRepository = environmentRepository,
|
||||
featureFlagManager = featureFlagManager,
|
||||
settingsRepository = settingsRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
clock = FIXED_CLOCK,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -180,3 +223,8 @@ private val DEFAULT_STATE =
|
||||
shouldShowRemindMeLater = true,
|
||||
dialogState = null,
|
||||
)
|
||||
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
@@ -119,6 +119,7 @@ private val DEFAULT_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
FlagKey.AppReviewPrompt to true,
|
||||
FlagKey.NewDeviceTemporaryDismiss to true,
|
||||
FlagKey.NewDevicePermanentDismiss to true,
|
||||
FlagKey.IgnoreEnvironmentCheck to true,
|
||||
)
|
||||
|
||||
private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
@@ -134,6 +135,7 @@ private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
FlagKey.AppReviewPrompt to false,
|
||||
FlagKey.NewDeviceTemporaryDismiss to false,
|
||||
FlagKey.NewDevicePermanentDismiss to false,
|
||||
FlagKey.IgnoreEnvironmentCheck to false,
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = DebugMenuState(
|
||||
|
||||
@@ -1187,7 +1187,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
@@ -1215,7 +1215,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1385,7 +1385,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
@@ -1414,7 +1414,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1440,7 +1440,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
canDelete = true,
|
||||
canAssociateToCollections = false,
|
||||
canAssociateToCollections = true,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1452,7 +1452,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
@@ -1481,7 +1481,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,13 +155,11 @@ class CollectionViewExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `canAssociateToCollections should return false if the user has except password permission at least one collection`() {
|
||||
fun `canAssociateToCollections should return false if the user doesn't have any manage or edit permissions`() {
|
||||
val collectionList: List<CollectionView> = listOf(
|
||||
createEditExceptPasswordsCollectionView(number = 1),
|
||||
createViewCollectionView(number = 2),
|
||||
createViewExceptPasswordsCollectionView(number = 3),
|
||||
createManageCollectionView(number = 4),
|
||||
createEditCollectionView(number = 5),
|
||||
)
|
||||
val collectionIds = collectionList.mapNotNull { it.id }
|
||||
assertFalse(collectionList.canAssignToCollections(collectionIds))
|
||||
|
||||
Reference in New Issue
Block a user