mirror of
https://github.com/bitwarden/android.git
synced 2026-06-02 02:36:58 -05:00
BIT-189 Check for data breaches during create account (#154)
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.HaveIBeenPwnedApi
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
|
||||
class HaveIBeenPwnedServiceTest : BaseServiceTest() {
|
||||
|
||||
private val haveIBeenPwnedApi: HaveIBeenPwnedApi = retrofit.create()
|
||||
private val service = HaveIBeenPwnedServiceImpl(haveIBeenPwnedApi)
|
||||
|
||||
@Test
|
||||
fun `when service returns failure should return failure`() = runTest {
|
||||
val response = MockResponse().setResponseCode(400)
|
||||
server.enqueue(response)
|
||||
assertTrue(service.hasPasswordBeenBreached(PWNED_PASSWORD).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when given password is in response returns true`() = runTest {
|
||||
val response = MockResponse().setBody(HIBP_RESPONSE)
|
||||
server.enqueue(response)
|
||||
val result = service.hasPasswordBeenBreached(PWNED_PASSWORD)
|
||||
assertTrue(result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when given password is not in response returns false`() = runTest {
|
||||
val response = MockResponse().setBody(HIBP_RESPONSE)
|
||||
server.enqueue(response)
|
||||
val result = service.hasPasswordBeenBreached("testpassword")
|
||||
assertFalse(result.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
private const val PWNED_PASSWORD = "password1234"
|
||||
|
||||
private val HIBP_RESPONSE = """
|
||||
FBD6D76BB5D2041542D7D2E3FAC5BB05593:36865
|
||||
F390F21EBEFEF07A1DA4E661AF830FD76A6:3
|
||||
F3CAEF537A4881A05E2A9A9A8A236FE7C14:1
|
||||
F44FD6981B10EC24A93989A0C61E71C767C:5
|
||||
""".trimIndent()
|
||||
@@ -14,6 +14,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJs
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
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.repository.model.AuthState
|
||||
@@ -22,6 +23,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
@@ -45,6 +47,7 @@ class AuthRepositoryTest {
|
||||
|
||||
private val accountsService: AccountsService = mockk()
|
||||
private val identityService: IdentityService = mockk()
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService = mockk()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val authSdkSource = mockk<AuthSdkSource> {
|
||||
coEvery {
|
||||
@@ -76,6 +79,7 @@ class AuthRepositoryTest {
|
||||
private val repository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
identityService = identityService,
|
||||
haveIBeenPwnedService = haveIBeenPwnedService,
|
||||
authSdkSource = authSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
dispatcher = UnconfinedTestDispatcher(),
|
||||
@@ -83,7 +87,7 @@ class AuthRepositoryTest {
|
||||
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
clearMocks(identityService, accountsService)
|
||||
clearMocks(identityService, accountsService, haveIBeenPwnedService)
|
||||
mockkStatic(GET_TOKEN_RESPONSE_EXTENSIONS_PATH)
|
||||
}
|
||||
|
||||
@@ -230,6 +234,72 @@ class AuthRepositoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register check data breaches error should return Error`() = runTest {
|
||||
coEvery {
|
||||
haveIBeenPwnedService.hasPasswordBeenBreached(PASSWORD)
|
||||
} returns Result.failure(Throwable())
|
||||
|
||||
val result = repository.register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = true,
|
||||
)
|
||||
assertEquals(RegisterResult.Error(null), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register check data breaches found should return DataBreachFound`() = runTest {
|
||||
coEvery {
|
||||
haveIBeenPwnedService.hasPasswordBeenBreached(PASSWORD)
|
||||
} returns true.asSuccess()
|
||||
|
||||
val result = repository.register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = true,
|
||||
)
|
||||
assertEquals(RegisterResult.DataBreachFound, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register check data breaches Success should return Success`() = runTest {
|
||||
coEvery {
|
||||
haveIBeenPwnedService.hasPasswordBeenBreached(PASSWORD)
|
||||
} returns false.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
masterPasswordHint = null,
|
||||
captchaResponse = null,
|
||||
key = ENCRYPTED_USER_KEY,
|
||||
keys = RegisterRequestJson.Keys(
|
||||
publicKey = PUBLIC_KEY,
|
||||
encryptedPrivateKey = PRIVATE_KEY,
|
||||
),
|
||||
kdfType = PBKDF2_SHA256,
|
||||
kdfIterations = DEFAULT_KDF_ITERATIONS.toUInt(),
|
||||
),
|
||||
)
|
||||
} returns Result.success(RegisterResponseJson.Success(captchaBypassToken = CAPTCHA_KEY))
|
||||
|
||||
val result = repository.register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = true,
|
||||
)
|
||||
assertEquals(RegisterResult.Success(CAPTCHA_KEY), result)
|
||||
coVerify { haveIBeenPwnedService.hasPasswordBeenBreached(PASSWORD) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register Success should return Success`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
@@ -256,6 +326,7 @@ class AuthRepositoryTest {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
assertEquals(RegisterResult.Success(CAPTCHA_KEY), result)
|
||||
}
|
||||
@@ -295,6 +366,7 @@ class AuthRepositoryTest {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
assertEquals(RegisterResult.Error(errorMessage = null), result)
|
||||
}
|
||||
@@ -334,6 +406,7 @@ class AuthRepositoryTest {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
assertEquals(RegisterResult.CaptchaRequired(captchaId = CAPTCHA_KEY), result)
|
||||
}
|
||||
@@ -364,6 +437,7 @@ class AuthRepositoryTest {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
assertEquals(RegisterResult.Error(errorMessage = null), result)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ 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.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
@@ -312,9 +311,11 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||
every { stateFlow } returns MutableStateFlow(
|
||||
DEFAULT_STATE.copy(
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = "title".asText(),
|
||||
message = "message".asText(),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = "title".asText(),
|
||||
message = "message".asText(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -335,14 +336,62 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||
verify { viewModel.trySendAction(CreateAccountAction.ErrorDialogDismiss) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking No on the HIBP dialog should send ErrorDialogDismiss action`() {
|
||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||
every { stateFlow } returns MutableStateFlow(
|
||||
DEFAULT_STATE.copy(dialog = CreateAccountDialog.HaveIBeenPwned),
|
||||
)
|
||||
every { eventFlow } returns emptyFlow()
|
||||
every { trySendAction(CreateAccountAction.ErrorDialogDismiss) } returns Unit
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
CreateAccountScreen(
|
||||
onNavigateBack = {},
|
||||
onNavigateToLogin = { _, _ -> },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onAllNodesWithText("No")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(CreateAccountAction.ErrorDialogDismiss) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking Yes on the HIBP dialog should send ContinueWithBreachedPasswordClick action`() {
|
||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||
every { stateFlow } returns MutableStateFlow(
|
||||
DEFAULT_STATE.copy(dialog = CreateAccountDialog.HaveIBeenPwned),
|
||||
)
|
||||
every { eventFlow } returns emptyFlow()
|
||||
every { trySendAction(CreateAccountAction.ErrorDialogDismiss) } returns Unit
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
CreateAccountScreen(
|
||||
onNavigateBack = {},
|
||||
onNavigateToLogin = { _, _ -> },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Yes")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(CreateAccountAction.ContinueWithBreachedPasswordClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when BasicDialogState is Shown should show dialog`() {
|
||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||
every { stateFlow } returns MutableStateFlow(
|
||||
DEFAULT_STATE.copy(
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = "title".asText(),
|
||||
message = "message".asText(),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = "title".asText(),
|
||||
message = "message".asText(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -407,8 +456,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||
passwordHintInput = "",
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = false,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
dialog = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.Pas
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
@@ -70,8 +70,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
passwordHintInput = "hint",
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = false,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
dialog = null,
|
||||
)
|
||||
val handle = SavedStateHandle(mapOf("state" to savedState))
|
||||
val viewModel = CreateAccountViewModel(
|
||||
@@ -91,9 +90,11 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
viewModel.trySendAction(EmailInputChange(input))
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
emailInput = input,
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.invalid_email.asText(),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.invalid_email.asText(),
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
@@ -112,10 +113,12 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
viewModel.trySendAction(EmailInputChange(input))
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
emailInput = input,
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.validation_field_required
|
||||
.asText(R.string.email_address.asText()),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.validation_field_required
|
||||
.asText(R.string.email_address.asText()),
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
@@ -136,9 +139,11 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
emailInput = EMAIL,
|
||||
passwordInput = input,
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.master_password_length_val_message_x.asText(12),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.master_password_length_val_message_x.asText(12),
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
@@ -159,9 +164,11 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
emailInput = "test@test.com",
|
||||
passwordInput = input,
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.master_password_confirmation_val_message.asText(),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.master_password_confirmation_val_message.asText(),
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
@@ -184,9 +191,11 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
emailInput = "test@test.com",
|
||||
passwordInput = password,
|
||||
confirmPasswordInput = password,
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.accept_policies_error.asText(),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.accept_policies_error.asText(),
|
||||
),
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
@@ -205,6 +214,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
} returns RegisterResult.Success(captchaToken = "mock_token")
|
||||
}
|
||||
@@ -218,11 +228,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem())
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
assertEquals(
|
||||
VALID_INPUT_STATE.copy(
|
||||
loadingDialogState = LoadingDialogState.Shown(
|
||||
text = R.string.creating_account.asText(),
|
||||
),
|
||||
),
|
||||
VALID_INPUT_STATE.copy(dialog = CreateAccountDialog.Loading),
|
||||
stateFlow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
@@ -247,6 +253,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
} returns RegisterResult.Error(errorMessage = "mock_error")
|
||||
}
|
||||
@@ -258,19 +265,16 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(VALID_INPUT_STATE, awaitItem())
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
assertEquals(
|
||||
VALID_INPUT_STATE.copy(
|
||||
loadingDialogState = LoadingDialogState.Shown(
|
||||
text = R.string.creating_account.asText(),
|
||||
),
|
||||
),
|
||||
VALID_INPUT_STATE.copy(dialog = CreateAccountDialog.Loading),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
VALID_INPUT_STATE.copy(
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
errorDialogState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = "mock_error".asText(),
|
||||
dialog = CreateAccountDialog.Error(
|
||||
BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = "mock_error".asText(),
|
||||
),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
@@ -292,6 +296,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
} returns RegisterResult.CaptchaRequired(captchaId = "mock_captcha_id")
|
||||
}
|
||||
@@ -322,6 +327,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
} returns RegisterResult.Success(captchaToken = "mock_captcha_token")
|
||||
}
|
||||
@@ -341,6 +347,69 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `ContinueWithBreachedPasswordClick should call repository with checkDataBreaches false`() {
|
||||
val repo = mockk<AuthRepository> {
|
||||
every { captchaTokenResultFlow } returns flowOf()
|
||||
coEvery {
|
||||
register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
} returns RegisterResult.Error(null)
|
||||
}
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = validInputHandle,
|
||||
authRepository = repo,
|
||||
)
|
||||
viewModel.trySendAction(CreateAccountAction.ContinueWithBreachedPasswordClick)
|
||||
coVerify {
|
||||
repo.register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SubmitClick register returns ShowDataBreaches should show HaveIBeenPwned dialog`() =
|
||||
runTest {
|
||||
val repo = mockk<AuthRepository> {
|
||||
every { captchaTokenResultFlow } returns flowOf()
|
||||
coEvery {
|
||||
register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = true,
|
||||
)
|
||||
} returns RegisterResult.DataBreachFound
|
||||
}
|
||||
val viewModel = CreateAccountViewModel(
|
||||
savedStateHandle = validInputHandle,
|
||||
authRepository = repo,
|
||||
)
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.CheckDataBreachesToggle(true))
|
||||
viewModel.actionChannel.trySend(CreateAccountAction.SubmitClick)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
VALID_INPUT_STATE.copy(
|
||||
isCheckDataBreachesToggled = true,
|
||||
dialog = CreateAccountDialog.HaveIBeenPwned,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CloseClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = CreateAccountViewModel(
|
||||
@@ -459,8 +528,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
passwordHintInput = "",
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = false,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
dialog = null,
|
||||
)
|
||||
private val VALID_INPUT_STATE = CreateAccountState(
|
||||
passwordInput = PASSWORD,
|
||||
@@ -469,8 +537,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||
passwordHintInput = "",
|
||||
isCheckDataBreachesToggled = false,
|
||||
isAcceptPoliciesToggled = true,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
dialog = null,
|
||||
)
|
||||
private const val LOGIN_RESULT_PATH =
|
||||
"com.x8bit.bitwarden.data.auth.repository.util.CaptchaUtilsKt"
|
||||
|
||||
Reference in New Issue
Block a user