BIT-363, BIT-1323: Add time interval options to session timeout menu (#529)

This commit is contained in:
Brian Yencho
2024-01-08 09:19:55 -06:00
committed by GitHub
parent 8e95277e89
commit de9e32f6aa
8 changed files with 428 additions and 52 deletions

View File

@@ -15,10 +15,12 @@ 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.model.VaultTimeout
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@@ -124,29 +126,217 @@ class AccountSecurityScreenTest : BaseComposeTest() {
composeTestRule.onNodeWithText("Unlock with PIN code").assertIsOn()
}
@Test
fun `on session timeout click should send SessionTimeoutClick`() {
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
verify { viewModel.trySendAction(AccountSecurityAction.SessionTimeoutClick) }
}
@Test
fun `session timeout should be updated on or off according to state`() {
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.assertTextEquals("Session timeout", "15 Minutes")
mutableStateFlow.update { it.copy(sessionTimeout = "30 Minutes".asText()) }
.assertTextEquals("Session timeout", "30 minutes")
mutableStateFlow.update { it.copy(vaultTimeoutType = VaultTimeout.Type.FOUR_HOURS) }
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.assertTextEquals("Session timeout", "30 Minutes")
.assertTextEquals("Session timeout", "4 hours")
}
@Test
fun `on session timeout click should show a selection dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Immediately")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("1 minute")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("5 minutes")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("30 minutes")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("1 hour")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("4 hours")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("On app restart")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Custom")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on session timeout selection dialog cancel click should close the dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on session timeout selection non-Never timeout type click should send VaultTimeoutTypeSelect and close the dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("4 hours")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify {
viewModel.trySendAction(
AccountSecurityAction.VaultTimeoutTypeSelect(VaultTimeout.Type.FOUR_HOURS),
)
}
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on session timeout selection Never timeout type click should show a confirmation dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Warning")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText(
"Setting your lock options to “Never” keeps your vault available to anyone with " +
"access to your device. If you use this option, you should ensure that you " +
"keep your device properly protected.",
)
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on session timeout Never confirmation dialog Cancel click should close the dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Warning")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify(exactly = 0) { viewModel.trySendAction(any()) }
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on session timeout Never confirmation dialog Ok click should close the dialog and emit VaultTimeoutTypeSelect`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onAllNodesWithText("Session timeout")
.filterToOne(hasClickAction())
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Never")
.filterToOne(hasAnyAncestor(isDialog()))
.performScrollTo()
.performClick()
composeTestRule
.onAllNodesWithText("Warning")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify {
viewModel.trySendAction(
AccountSecurityAction.VaultTimeoutTypeSelect(VaultTimeout.Type.NEVER),
)
}
composeTestRule.assertNoDialogExists()
}
@Test
@@ -337,7 +527,7 @@ class AccountSecurityScreenTest : BaseComposeTest() {
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
sessionTimeout = "15 Minutes".asText(),
vaultTimeoutType = VaultTimeout.Type.THIRTY_MINUTES,
sessionTimeoutAction = SessionTimeoutAction.LOCK,
)
}

View File

@@ -3,6 +3,8 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
@@ -119,6 +121,30 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `on VaultTimeoutTypeSelect should update the selection and emit ShowToast()`() = runTest {
val settingsRepository = mockk<SettingsRepository>() {
every { vaultTimeout = any() } just runs
}
val viewModel = createViewModel(settingsRepository = settingsRepository)
viewModel.eventFlow.test {
viewModel.trySendAction(
AccountSecurityAction.VaultTimeoutTypeSelect(VaultTimeout.Type.FOUR_HOURS),
)
assertEquals(
AccountSecurityEvent.ShowToast("Not yet implemented.".asText()),
awaitItem(),
)
}
assertEquals(
DEFAULT_STATE.copy(
vaultTimeoutType = VaultTimeout.Type.FOUR_HOURS,
),
viewModel.stateFlow.value,
)
verify { settingsRepository.vaultTimeout = VaultTimeout.FourHours }
}
@Test
fun `on SessionTimeoutActionSelect should update session timeout action`() = runTest {
val viewModel = createViewModel()
@@ -148,18 +174,6 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `on SessionTimeoutClick should emit ShowToast`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AccountSecurityAction.SessionTimeoutClick)
assertEquals(
AccountSecurityEvent.ShowToast("Display session timeout dialog.".asText()),
awaitItem(),
)
}
}
@Test
fun `on TwoStepLoginClick should emit NavigateToTwoStepLogin`() = runTest {
val viewModel = createViewModel()
@@ -235,12 +249,14 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
private fun createViewModel(
authRepository: AuthRepository = mockk(relaxed = true),
vaultRepository: VaultRepository = mockk(relaxed = true),
settingsRepository: SettingsRepository = mockk(relaxed = true),
savedStateHandle: SavedStateHandle = SavedStateHandle().apply {
set("state", DEFAULT_STATE)
},
): AccountSecurityViewModel = AccountSecurityViewModel(
authRepository = authRepository,
vaultRepository = vaultRepository,
settingsRepository = settingsRepository,
savedStateHandle = savedStateHandle,
)
@@ -251,7 +267,7 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
sessionTimeout = "15 Minutes".asText(),
vaultTimeoutType = VaultTimeout.Type.THIRTY_MINUTES,
sessionTimeoutAction = SessionTimeoutAction.LOCK,
)
}

View File

@@ -0,0 +1,30 @@
package com.x8bit.bitwarden.ui.platform.util
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.ui.platform.base.util.asText
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class VaultTimeoutExtensionsTest {
@Test
fun `displayLabel should return the correct value for each type`() {
mapOf(
VaultTimeout.Type.IMMEDIATELY to R.string.immediately.asText(),
VaultTimeout.Type.ONE_MINUTE to R.string.one_minute.asText(),
VaultTimeout.Type.FIVE_MINUTES to R.string.five_minutes.asText(),
VaultTimeout.Type.THIRTY_MINUTES to R.string.thirty_minutes.asText(),
VaultTimeout.Type.ONE_HOUR to R.string.one_hour.asText(),
VaultTimeout.Type.FOUR_HOURS to R.string.four_hours.asText(),
VaultTimeout.Type.ON_APP_RESTART to R.string.on_restart.asText(),
VaultTimeout.Type.NEVER to R.string.never.asText(),
VaultTimeout.Type.CUSTOM to R.string.custom.asText(),
)
.forEach { (type, label) ->
assertEquals(
label,
type.displayLabel,
)
}
}
}