PM-19645: Remove new device feature flags (#4950)

This commit is contained in:
David Perez
2025-04-02 07:59:48 -05:00
committed by GitHub
parent a476436bff
commit e5ddeb44fd
14 changed files with 34 additions and 300 deletions

View File

@@ -1420,34 +1420,28 @@ class AuthRepositoryImpl(
}
override fun checkUserNeedsNewDeviceTwoFactorNotice(): Boolean {
return activeUserId?.let { userId ->
val temporaryFlag = featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
val permanentFlag = featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
return activeUserId
?.let { userId ->
if (!newDeviceNoticePreConditionsValid()) {
return false
}
// check if feature flags are disabled
if (!temporaryFlag && !permanentFlag) {
return false
val newDeviceNoticeState = authDiskSource.getNewDeviceNoticeState(userId = userId)
return when (newDeviceNoticeState.displayStatus) {
// if the user has already attested email access but permanent flag is enabled,
// the notice needs to appear again
NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL -> true
// if the user has already seen but 7 days have already passed,
// the notice needs to appear again
NewDeviceNoticeDisplayStatus.HAS_SEEN -> {
newDeviceNoticeState.shouldDisplayNoticeIfSeen
}
NewDeviceNoticeDisplayStatus.HAS_NOT_SEEN -> true
// the user never needs to see the notice again
NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL_PERMANENT -> false
}
}
if (!newDeviceNoticePreConditionsValid()) {
return false
}
val newDeviceNoticeState = authDiskSource.getNewDeviceNoticeState(userId = userId)
return when (newDeviceNoticeState.displayStatus) {
// if the user has already attested email access but permanent flag is enabled,
// the notice needs to appear again
NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL -> permanentFlag
// if the user has already seen but 7 days have already passed,
// the notice needs to appear again
NewDeviceNoticeDisplayStatus.HAS_SEEN ->
newDeviceNoticeState.shouldDisplayNoticeIfSeen
NewDeviceNoticeDisplayStatus.HAS_NOT_SEEN -> true
// the user never needs to see the notice again
NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL_PERMANENT -> false
}
}
?: false
}

View File

@@ -35,8 +35,6 @@ sealed class FlagKey<out T : Any> {
CredentialExchangeProtocolImport,
CredentialExchangeProtocolExport,
AppReviewPrompt,
NewDevicePermanentDismiss,
NewDeviceTemporaryDismiss,
IgnoreEnvironmentCheck,
MutualTls,
SingleTapPasskeyCreation,
@@ -160,24 +158,6 @@ sealed class FlagKey<out T : Any> {
override val isRemotelyConfigured: Boolean = true
}
/**
* Data object holding the feature flag key for the New Device Temporary Dismiss feature.
*/
data object NewDeviceTemporaryDismiss : FlagKey<Boolean>() {
override val keyName: String = "new-device-temporary-dismiss"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = true
}
/**
* Data object holding the feature flag key for the New Device Permanent Dismiss feature.
*/
data object NewDevicePermanentDismiss : FlagKey<Boolean>() {
override val keyName: String = "new-device-permanent-dismiss"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = true
}
/**
* Data object holding the feature flag key to ignore an environment check.
*/

View File

@@ -3,12 +3,9 @@ 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
@@ -30,7 +27,6 @@ private const val KEY_STATE = "state"
class NewDeviceNoticeEmailAccessViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val vaultRepository: VaultRepository,
private val featureFlagManager: FeatureFlagManager,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<
NewDeviceNoticeEmailAccessState,
@@ -62,16 +58,9 @@ class NewDeviceNoticeEmailAccessViewModel @Inject constructor(
private fun handleContinueClick() {
if (state.isEmailAccessEnabled) {
val displayStatus =
if (featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)) {
CAN_ACCESS_EMAIL_PERMANENT
} else {
CAN_ACCESS_EMAIL
}
authRepository.setNewDeviceNoticeState(
NewDeviceNoticeState(
displayStatus = displayStatus,
displayStatus = CAN_ACCESS_EMAIL_PERMANENT,
lastSeenDate = null,
),
)

View File

@@ -32,7 +32,6 @@ import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFac
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.ContinueDialogClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.DismissDialogClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.NavigateBackClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.RemindMeLaterClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.TurnOnTwoFactorClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorEvent.NavigateBackToVault
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorEvent.NavigateToChangeAccountEmail
@@ -124,10 +123,6 @@ fun NewDeviceNoticeTwoFactorScreen(
onChangeAccountEmailClick = {
viewModel.trySendAction(ChangeAccountEmailClick)
},
onRemindMeLaterClick = {
viewModel.trySendAction(RemindMeLaterClick)
},
state = state,
)
}
}
@@ -139,8 +134,6 @@ fun NewDeviceNoticeTwoFactorScreen(
private fun NewDeviceNoticeTwoFactorContent(
onTurnOnTwoFactorClick: () -> Unit,
onChangeAccountEmailClick: () -> Unit,
onRemindMeLaterClick: () -> Unit,
state: NewDeviceNoticeTwoFactorState,
modifier: Modifier = Modifier,
) {
Column(
@@ -156,8 +149,6 @@ private fun NewDeviceNoticeTwoFactorContent(
MainContent(
onTurnOnTwoFactorClick = onTurnOnTwoFactorClick,
onChangeAccountEmailClick = onChangeAccountEmailClick,
onRemindMeLaterClick = onRemindMeLaterClick,
state = state,
)
Spacer(modifier = Modifier.navigationBarsPadding())
}
@@ -199,8 +190,6 @@ private fun ColumnScope.HeaderContent() {
private fun ColumnScope.MainContent(
onTurnOnTwoFactorClick: () -> Unit,
onChangeAccountEmailClick: () -> Unit,
onRemindMeLaterClick: () -> Unit,
state: NewDeviceNoticeTwoFactorState,
) {
BitwardenFilledButton(
label = stringResource(R.string.turn_on_two_step_login),
@@ -217,15 +206,6 @@ private fun ColumnScope.MainContent(
modifier = Modifier
.fillMaxWidth(),
)
if (state.shouldShowRemindMeLater) {
Spacer(modifier = Modifier.height(12.dp))
BitwardenOutlinedButton(
label = stringResource(R.string.remind_me_later),
onClick = onRemindMeLaterClick,
modifier = Modifier
.fillMaxWidth(),
)
}
}
@PreviewScreenSizes
@@ -235,10 +215,6 @@ private fun NewDeviceNoticeTwoFactorScreen_preview() {
NewDeviceNoticeTwoFactorContent(
onTurnOnTwoFactorClick = {},
onChangeAccountEmailClick = {},
onRemindMeLaterClick = {},
state = NewDeviceNoticeTwoFactorState(
shouldShowRemindMeLater = true,
),
)
}
}

View File

@@ -3,11 +3,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
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
@@ -16,7 +12,6 @@ import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFac
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.ContinueDialogClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.DismissDialogClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.NavigateBackClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.RemindMeLaterClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorAction.TurnOnTwoFactorClick
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorDialogState.ChangeAccountEmailDialog
import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeTwoFactorDialogState.TurnOnTwoFactorDialog
@@ -27,8 +22,6 @@ 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
/**
@@ -38,19 +31,15 @@ import javax.inject.Inject
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,
NewDeviceNoticeTwoFactorAction,
>(
initialState = NewDeviceNoticeTwoFactorState(
shouldShowRemindMeLater = !featureFlagManager.getFeatureFlag(
FlagKey.NewDevicePermanentDismiss,
),
dialogState = null,
),
) {
init {
@@ -86,8 +75,6 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
TurnOnTwoFactorClick -> updateDialogState(newState = TurnOnTwoFactorDialog)
RemindMeLaterClick -> handleRemindMeLater()
DismissDialogClick -> updateDialogState(newState = null)
ContinueDialogClick -> handleContinueDialog()
@@ -96,16 +83,6 @@ class NewDeviceNoticeTwoFactorViewModel @Inject constructor(
}
}
private fun handleRemindMeLater() {
authRepository.setNewDeviceNoticeState(
NewDeviceNoticeState(
displayStatus = NewDeviceNoticeDisplayStatus.HAS_SEEN,
lastSeenDate = ZonedDateTime.now(clock),
),
)
sendEvent(NewDeviceNoticeTwoFactorEvent.NavigateBackToVault)
}
private fun handleContinueDialog() {
when (state.dialogState) {
is ChangeAccountEmailDialog -> {
@@ -178,11 +155,6 @@ sealed class NewDeviceNoticeTwoFactorAction {
*/
data object ChangeAccountEmailClick : NewDeviceNoticeTwoFactorAction()
/**
* User tapped the remind me later button.
*/
data object RemindMeLaterClick : NewDeviceNoticeTwoFactorAction()
/**
* User tapped the dismiss dialog button.
*/
@@ -204,8 +176,7 @@ sealed class NewDeviceNoticeTwoFactorAction {
*/
@Parcelize
data class NewDeviceNoticeTwoFactorState(
val dialogState: NewDeviceNoticeTwoFactorDialogState? = null,
val shouldShowRemindMeLater: Boolean,
val dialogState: NewDeviceNoticeTwoFactorDialogState?,
) : Parcelable
/**

View File

@@ -35,8 +35,6 @@ fun <T : Any> FlagKey<T>.ListItemContent(
FlagKey.CredentialExchangeProtocolExport,
FlagKey.AppReviewPrompt,
FlagKey.CipherKeyEncryption,
FlagKey.NewDevicePermanentDismiss,
FlagKey.NewDeviceTemporaryDismiss,
FlagKey.IgnoreEnvironmentCheck,
FlagKey.MutualTls,
FlagKey.SingleTapPasskeyCreation,
@@ -97,8 +95,6 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
FlagKey.CredentialExchangeProtocolExport -> stringResource(R.string.cxp_export)
FlagKey.AppReviewPrompt -> stringResource(R.string.app_review_prompt)
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)
FlagKey.MutualTls -> stringResource(R.string.mutual_tls)
FlagKey.SingleTapPasskeyCreation -> stringResource(R.string.single_tap_passkey_creation)

View File

@@ -966,7 +966,6 @@ Do you want to switch to this account?</string>
<string name="your_passkey_will_be_saved_to_your_bitwarden_vault_for_x">Your passkey will be saved to your Bitwarden vault for %1$s</string>
<string name="organization_unassigned_items_message_useu_description_long">Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible.</string>
<string name="organization_unassigned_items_message_self_host_041624_description_long">On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible.</string>
<string name="remind_me_later">Remind me later</string>
<string name="notice">Notice</string>
<string name="passkeys_not_supported_for_this_app">Passkeys not supported for this app</string>
<string name="passkey_operation_failed_because_browser_is_not_privileged">Passkey operation failed because browser is not privileged</string>

View File

@@ -22,8 +22,6 @@
<string name="restart_onboarding_carousel_details">This will force the change to app state which will cause the first time carousel to show. The carousel will continue to show for any \"new\" account until a login is completed. May need to exit debug menu manually.</string>
<string name="app_review_prompt">App Review Prompt</string>
<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>">
<string name="reset_coach_mark_tour_status">Reset all coach mark tours</string>
<string name="anon_addy_self_hosted_aliases">AnonAddy self-hosted aliases</string>

View File

@@ -253,8 +253,6 @@ 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
}
@@ -1580,7 +1578,6 @@ class AuthRepositoryTest {
coVerify { identityService.preLogin(email = EMAIL) }
}
@Suppress("MaxLineLength")
@Test
fun `prelogin fails should return CertificateError when SSLHandshakeException is thrown`() =
runTest {
@@ -1753,7 +1750,6 @@ class AuthRepositoryTest {
}
@Test
@Suppress("MaxLineLength")
fun `login should return Error result when get token succeeds but unlock vault fails`() =
runTest {
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
@@ -5389,7 +5385,6 @@ class AuthRepositoryTest {
)
}
@Suppress("MaxLineLength")
@Test
fun `getVerifiedOrganizationDomainSsoDetails Success should return Success`() = runTest {
val email = "test@gmail.com"
@@ -5408,6 +5403,7 @@ class AuthRepositoryTest {
assertEquals(
VerifiedOrganizationDomainSsoDetailsResult.Success(
verifiedOrganizationDomainSsoDetails = listOf(
@Suppress("MaxLineLength")
VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail(
organizationIdentifier = "Test Identifier",
organizationName = "Bitwarden",
@@ -6483,7 +6479,6 @@ class AuthRepositoryTest {
assertNull(fakeAuthDiskSource.getOnboardingStatus(USER_ID_1))
}
@Suppress("MaxLineLength")
@Test
fun `on successful login does not set onboarding status if feature flag is off`() =
runTest {
@@ -6675,28 +6670,6 @@ class AuthRepositoryTest {
assertTrue(shouldShowNewDeviceNotice)
}
@Test
@Suppress("MaxLineLength")
fun `checkUserNeedsNewDeviceTwoFactorNotice NewDeviceTemporaryDismiss and NewDevicePermanentDismiss flags are off returns false`() =
runTest {
every {
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
} returns false
every {
featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
} returns false
every {
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
} returns listOf()
fakeEnvironmentRepository.environment = Environment.Us
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
val shouldShowNewDeviceNotice = repository.checkUserNeedsNewDeviceTwoFactorNotice()
assertFalse(shouldShowNewDeviceNotice)
}
@Test
@Suppress("MaxLineLength")
fun `checkUserNeedsNewDeviceTwoFactorNotice IgnoreEnvironmentCheck flag enabled should not check for a cloud environment and return true`() =
@@ -6719,7 +6692,6 @@ class AuthRepositoryTest {
}
@Test
@Suppress("MaxLineLength")
fun `checkUserNeedsNewDeviceTwoFactorNotice if environment is selfhosted return false`() =
runTest {
every {
@@ -6890,7 +6862,7 @@ class AuthRepositoryTest {
@Test
@Suppress("MaxLineLength")
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus CAN_ACCESS_EMAIL return permanent flag value`() =
fun `checkUserNeedsNewDeviceTwoFactorNotice with NewDeviceNoticeDisplayStatus CAN_ACCESS_EMAIL return true`() =
runTest {
every {
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
@@ -6907,12 +6879,6 @@ class AuthRepositoryTest {
)
assertTrue(repository.checkUserNeedsNewDeviceTwoFactorNotice())
every {
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
} returns false
assertFalse(repository.checkUserNeedsNewDeviceTwoFactorNotice())
}
@Test

View File

@@ -49,14 +49,6 @@ class FlagKeyTest {
FlagKey.CipherKeyEncryption.keyName,
"cipher-key-encryption",
)
assertEquals(
FlagKey.NewDeviceTemporaryDismiss.keyName,
"new-device-temporary-dismiss",
)
assertEquals(
FlagKey.NewDevicePermanentDismiss.keyName,
"new-device-permanent-dismiss",
)
assertEquals(
FlagKey.SingleTapPasskeyCreation.keyName,
"single-tap-passkey-creation",
@@ -104,8 +96,6 @@ class FlagKeyTest {
FlagKey.CredentialExchangeProtocolImport,
FlagKey.CredentialExchangeProtocolExport,
FlagKey.AppReviewPrompt,
FlagKey.NewDeviceTemporaryDismiss,
FlagKey.NewDevicePermanentDismiss,
FlagKey.SingleTapPasskeyCreation,
FlagKey.SingleTapPasskeyAuthentication,
FlagKey.AnonAddySelfHostAlias,
@@ -134,8 +124,6 @@ class FlagKeyTest {
FlagKey.CredentialExchangeProtocolExport,
FlagKey.AppReviewPrompt,
FlagKey.CipherKeyEncryption,
FlagKey.NewDeviceTemporaryDismiss,
FlagKey.NewDevicePermanentDismiss,
FlagKey.SingleTapPasskeyCreation,
FlagKey.SingleTapPasskeyAuthentication,
FlagKey.MutualTls,

View File

@@ -5,8 +5,6 @@ import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus
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
@@ -28,11 +26,6 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
every { checkUserNeedsNewDeviceTwoFactorNotice() } returns true
}
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
every { getFeatureFlag(FlagKey.NewDevicePermanentDismiss) } returns true
every { getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss) } returns true
}
private val vaultRepository = mockk<VaultRepository>(relaxed = true)
@Test
@@ -97,33 +90,6 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
}
}
@Test
@Suppress("MaxLineLength")
fun `ContinueClick should emit NavigateBackToVault if isEmailAccessEnabled and NewDevicePermanentDismiss flag is off`() =
runTest {
every {
featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
} returns false
val viewModel = createViewModel()
viewModel.trySendAction(NewDeviceNoticeEmailAccessAction.EmailAccessToggle(true))
viewModel.eventFlow.test {
viewModel.trySendAction(NewDeviceNoticeEmailAccessAction.ContinueClick)
assertEquals(
NewDeviceNoticeEmailAccessEvent.NavigateBackToVault,
awaitItem(),
)
verify(exactly = 1) {
authRepository.setNewDeviceNoticeState(
NewDeviceNoticeState(
displayStatus = NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL,
lastSeenDate = null,
),
)
}
}
}
@Test
fun `ContinueClick should emit NavigateToTwoFactorOptions if isEmailAccessEnabled is false`() =
runTest {
@@ -138,14 +104,13 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
}
@Test
fun `LearnMoreClick should emit NavigateToLearnMore`() =
runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(NewDeviceNoticeEmailAccessAction.LearnMoreClick)
assertEquals(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore, awaitItem())
}
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 {
@@ -153,7 +118,6 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() {
},
): NewDeviceNoticeEmailAccessViewModel = NewDeviceNoticeEmailAccessViewModel(
authRepository = authRepository,
featureFlagManager = featureFlagManager,
vaultRepository = vaultRepository,
savedStateHandle = savedStateHandle,
)

View File

@@ -94,19 +94,6 @@ class NewDeviceNoticeTwoFactorScreenTest : BaseComposeTest() {
}
}
@Test
fun `Remind me later click should send RemindMeLaterClick action`() {
composeTestRule
.onNodeWithText("Remind me later")
.performScrollTo()
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
NewDeviceNoticeTwoFactorAction.RemindMeLaterClick,
)
}
}
@Test
fun `on NavigateToTurnOnTwoFactor should call launchUri on IntentManager`() {
mutableEventFlow.tryEmit(
@@ -144,22 +131,6 @@ class NewDeviceNoticeTwoFactorScreenTest : BaseComposeTest() {
}
@Test
fun `remind me later button visibility should update according to state`() {
composeTestRule
.onNodeWithText("Remind me later")
.performScrollTo()
.assertIsDisplayed()
mutableStateFlow.update {
it.copy(shouldShowRemindMeLater = false)
}
composeTestRule
.onNodeWithText("Remind me later")
.assertDoesNotExist()
}
@Test
@Suppress("MaxLineLength")
fun `turn on two factor dialog should be shown or hidden according to the state`() {
composeTestRule.onNode(isDialog()).assertDoesNotExist()
@@ -175,9 +146,10 @@ class NewDeviceNoticeTwoFactorScreenTest : BaseComposeTest() {
.onNodeWithText("Continue to web app", substring = true, ignoreCase = true)
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
@Suppress("MaxLineLength")
composeTestRule
.onNodeWithText(
"Make your account more secure by setting up two-step login in the Bitwarden web app.",
text = "Make your account more secure by setting up two-step login in the Bitwarden web app.",
substring = true,
ignoreCase = true,
)
@@ -251,6 +223,5 @@ class NewDeviceNoticeTwoFactorScreenTest : BaseComposeTest() {
private val DEFAULT_STATE =
NewDeviceNoticeTwoFactorState(
shouldShowRemindMeLater = true,
dialogState = null,
)

View File

@@ -1,11 +1,7 @@
package com.x8bit.bitwarden.ui.auth.feature.newdevicenotice
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus
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
@@ -18,10 +14,6 @@ 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()
@@ -29,35 +21,18 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
every { checkUserNeedsNewDeviceTwoFactorNotice() } returns true
}
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
every { getFeatureFlag(FlagKey.NewDevicePermanentDismiss) } returns false
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 {
fun `initial state should be correct`() = runTest {
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(DEFAULT_STATE, awaitItem())
}
}
@Test
fun `initial state should be correct with NewDevicePermanentDismiss flag true`() = runTest {
every { featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss) } returns true
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE.copy(shouldShowRemindMeLater = false),
awaitItem(),
)
}
}
@Test
fun `Init should not send events if user needs new device notice`() = runTest {
every { authRepository.checkUserNeedsNewDeviceTwoFactorNotice() } returns true
@@ -126,26 +101,6 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `RemindMeLaterClick should emit NavigateBackToVault`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(NewDeviceNoticeTwoFactorAction.RemindMeLaterClick)
assertEquals(
NewDeviceNoticeTwoFactorEvent.NavigateBackToVault,
awaitItem(),
)
verify(exactly = 1) {
authRepository.setNewDeviceNoticeState(
NewDeviceNoticeState(
displayStatus = NewDeviceNoticeDisplayStatus.HAS_SEEN,
lastSeenDate = ZonedDateTime.now(FIXED_CLOCK),
),
)
}
}
}
@Test
fun `NavigateBackClick should send NavigateBack event`() = runTest {
val viewModel = createViewModel()
@@ -207,7 +162,6 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
}
@Test
@Suppress("MaxLineLength")
fun `ContinueDialogClick should return if dialog state is null`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
@@ -223,20 +177,12 @@ class NewDeviceNoticeTwoFactorViewModelTest : BaseViewModelTest() {
NewDeviceNoticeTwoFactorViewModel(
authRepository = authRepository,
environmentRepository = environmentRepository,
featureFlagManager = featureFlagManager,
settingsRepository = settingsRepository,
vaultRepository = vaultRepository,
clock = FIXED_CLOCK,
)
}
private val DEFAULT_STATE =
NewDeviceNoticeTwoFactorState(
shouldShowRemindMeLater = true,
dialogState = null,
)
private val FIXED_CLOCK: Clock = Clock.fixed(
Instant.parse("2023-10-27T12:00:00Z"),
ZoneOffset.UTC,
)

View File

@@ -129,8 +129,6 @@ private val DEFAULT_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf
FlagKey.CredentialExchangeProtocolImport to true,
FlagKey.CredentialExchangeProtocolExport to true,
FlagKey.AppReviewPrompt to true,
FlagKey.NewDeviceTemporaryDismiss to true,
FlagKey.NewDevicePermanentDismiss to true,
FlagKey.IgnoreEnvironmentCheck to true,
FlagKey.MutualTls to true,
FlagKey.SingleTapPasskeyCreation to true,
@@ -152,8 +150,6 @@ private val UPDATED_MAP_VALUE: ImmutableMap<FlagKey<Any>, Any> = persistentMapOf
FlagKey.CredentialExchangeProtocolImport to false,
FlagKey.CredentialExchangeProtocolExport to false,
FlagKey.AppReviewPrompt to false,
FlagKey.NewDeviceTemporaryDismiss to false,
FlagKey.NewDevicePermanentDismiss to false,
FlagKey.IgnoreEnvironmentCheck to false,
FlagKey.MutualTls to false,
FlagKey.SingleTapPasskeyCreation to false,