mirror of
https://github.com/bitwarden/android.git
synced 2026-06-01 10:16:47 -05:00
BIT-707 Implement password strength indicator with mock values (#161)
This commit is contained in:
@@ -17,6 +17,11 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_0
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_1
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_2
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_3
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
@@ -643,6 +648,27 @@ class AuthRepositoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getPasswordStrength should be based on password length`() = runTest {
|
||||
// TODO: Replace with SDK call (BIT-964)
|
||||
assertEquals(LEVEL_0.asSuccess(), repository.getPasswordStrength(EMAIL, "1"))
|
||||
assertEquals(LEVEL_0.asSuccess(), repository.getPasswordStrength(EMAIL, "12"))
|
||||
assertEquals(LEVEL_0.asSuccess(), repository.getPasswordStrength(EMAIL, "123"))
|
||||
|
||||
assertEquals(LEVEL_1.asSuccess(), repository.getPasswordStrength(EMAIL, "1234"))
|
||||
assertEquals(LEVEL_1.asSuccess(), repository.getPasswordStrength(EMAIL, "12345"))
|
||||
assertEquals(LEVEL_1.asSuccess(), repository.getPasswordStrength(EMAIL, "123456"))
|
||||
|
||||
assertEquals(LEVEL_2.asSuccess(), repository.getPasswordStrength(EMAIL, "1234567"))
|
||||
assertEquals(LEVEL_2.asSuccess(), repository.getPasswordStrength(EMAIL, "12345678"))
|
||||
assertEquals(LEVEL_2.asSuccess(), repository.getPasswordStrength(EMAIL, "123456789"))
|
||||
|
||||
assertEquals(LEVEL_3.asSuccess(), repository.getPasswordStrength(EMAIL, "123456789a"))
|
||||
assertEquals(LEVEL_3.asSuccess(), repository.getPasswordStrength(EMAIL, "123456789ab"))
|
||||
|
||||
assertEquals(LEVEL_4.asSuccess(), repository.getPasswordStrength(EMAIL, "123456789abc"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val GET_TOKEN_RESPONSE_EXTENSIONS_PATH =
|
||||
"com.x8bit.bitwarden.data.auth.repository.util.GetTokenResponseExtensionsKt"
|
||||
|
||||
@@ -33,6 +33,7 @@ import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@@ -408,6 +409,46 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||
composeTestRule.onNode(isDialog()).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `password strength should change as state changes`() {
|
||||
val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
every { eventFlow } returns emptyFlow()
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
CreateAccountScreen(
|
||||
onNavigateBack = {},
|
||||
onNavigateToLogin = { _, _ -> },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.WEAK_1)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Weak").assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.WEAK_2)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Weak").assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.WEAK_3)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Weak").assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.GOOD)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Good").assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.STRONG)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Strong").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toggling one password field visibility should toggle the other`() {
|
||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||
@@ -457,6 +498,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = false,
|
||||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,21 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import app.cash.turbine.turbineScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_0
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_1
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_2
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_3
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.AcceptPoliciesToggle
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CloseClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.Internal.ReceivePasswordStrengthResult
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
@@ -71,6 +79,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = false,
|
||||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
)
|
||||
val handle = SavedStateHandle(mapOf("state" to savedState))
|
||||
val viewModel = CreateAccountViewModel(
|
||||
@@ -129,13 +138,16 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `SubmitClick with password below 12 chars should show password length dialog`() = runTest {
|
||||
val input = "abcdefghikl"
|
||||
coEvery {
|
||||
mockAuthRepository.getPasswordStrength("test@test.com", input)
|
||||
} returns Throwable().asFailure()
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
authRepository = mockAuthRepository,
|
||||
)
|
||||
val input = "abcdefghikl"
|
||||
viewModel.trySendAction(EmailInputChange(EMAIL))
|
||||
viewModel.trySendAction(PasswordInputChange("abcdefghikl"))
|
||||
viewModel.trySendAction(PasswordInputChange(input))
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
emailInput = EMAIL,
|
||||
passwordInput = input,
|
||||
@@ -154,11 +166,14 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `SubmitClick with passwords not matching should show password match dialog`() = runTest {
|
||||
val input = "testtesttesttest"
|
||||
coEvery {
|
||||
mockAuthRepository.getPasswordStrength("test@test.com", input)
|
||||
} returns Throwable().asFailure()
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
authRepository = mockAuthRepository,
|
||||
)
|
||||
val input = "testtesttesttest"
|
||||
viewModel.trySendAction(EmailInputChange("test@test.com"))
|
||||
viewModel.trySendAction(PasswordInputChange(input))
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
@@ -179,11 +194,14 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `SubmitClick without policies accepted should show accept policies error`() = runTest {
|
||||
val password = "testtesttesttest"
|
||||
coEvery {
|
||||
mockAuthRepository.getPasswordStrength("test@test.com", password)
|
||||
} returns Throwable().asFailure()
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
authRepository = mockAuthRepository,
|
||||
)
|
||||
val password = "testtesttesttest"
|
||||
viewModel.trySendAction(EmailInputChange("test@test.com"))
|
||||
viewModel.trySendAction(PasswordInputChange(password))
|
||||
viewModel.trySendAction(ConfirmPasswordInputChange(password))
|
||||
@@ -483,7 +501,10 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PasswordInputChange update passwordInput`() = runTest {
|
||||
fun `PasswordInputChange update passwordInput and call getPasswordStrength`() = runTest {
|
||||
coEvery {
|
||||
mockAuthRepository.getPasswordStrength("", "input")
|
||||
} returns Result.failure(Throwable())
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
authRepository = mockAuthRepository,
|
||||
@@ -492,6 +513,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE.copy(passwordInput = "input"), awaitItem())
|
||||
}
|
||||
coVerify { mockAuthRepository.getPasswordStrength("", "input") }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -518,6 +540,62 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ReceivePasswordStrengthResult should update password strength state`() = runTest {
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
authRepository = mockAuthRepository,
|
||||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(ReceivePasswordStrengthResult(LEVEL_0.asSuccess()))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
passwordStrengthState = PasswordStrengthState.WEAK_1,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(ReceivePasswordStrengthResult(LEVEL_1.asSuccess()))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
passwordStrengthState = PasswordStrengthState.WEAK_2,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(ReceivePasswordStrengthResult(LEVEL_2.asSuccess()))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
passwordStrengthState = PasswordStrengthState.WEAK_3,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(ReceivePasswordStrengthResult(LEVEL_3.asSuccess()))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
passwordStrengthState = PasswordStrengthState.GOOD,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(ReceivePasswordStrengthResult(LEVEL_4.asSuccess()))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
passwordStrengthState = PasswordStrengthState.STRONG,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PASSWORD = "longenoughtpassword"
|
||||
private const val EMAIL = "test@test.com"
|
||||
@@ -529,6 +607,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
isCheckDataBreachesToggled = true,
|
||||
isAcceptPoliciesToggled = false,
|
||||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
)
|
||||
private val VALID_INPUT_STATE = CreateAccountState(
|
||||
passwordInput = PASSWORD,
|
||||
@@ -538,6 +617,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = true,
|
||||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.GOOD,
|
||||
)
|
||||
private const val LOGIN_RESULT_PATH =
|
||||
"com.x8bit.bitwarden.data.auth.repository.util.CaptchaUtilsKt"
|
||||
|
||||
Reference in New Issue
Block a user