PM-19294: Propagate the Register errors to the UI (#4883)

This commit is contained in:
David Perez
2025-03-17 14:54:43 -05:00
committed by GitHub
parent 4954e57007
commit ad8d8d271a
9 changed files with 38 additions and 14 deletions

View File

@@ -900,7 +900,10 @@ class AuthRepositoryImpl(
is RegisterResponseJson.CaptchaRequired -> {
it.validationErrors.captchaKeys.firstOrNull()
?.let { key -> RegisterResult.CaptchaRequired(captchaId = key) }
?: RegisterResult.Error(errorMessage = null)
?: RegisterResult.Error(
errorMessage = null,
error = MissingPropertyException("Captcha ID"),
)
}
is RegisterResponseJson.Success -> {
@@ -909,11 +912,11 @@ class AuthRepositoryImpl(
}
is RegisterResponseJson.Invalid -> {
RegisterResult.Error(errorMessage = it.message)
RegisterResult.Error(errorMessage = it.message, error = null)
}
}
},
onFailure = { RegisterResult.Error(errorMessage = null) },
onFailure = { RegisterResult.Error(errorMessage = null, error = it) },
)
}

View File

@@ -23,7 +23,10 @@ sealed class RegisterResult {
*
* @param errorMessage a message describing the error.
*/
data class Error(val errorMessage: String?) : RegisterResult()
data class Error(
val errorMessage: String?,
val error: Throwable?,
) : RegisterResult()
/**
* Password hash was found in a data breach.

View File

@@ -108,6 +108,7 @@ fun CompleteRegistrationScreen(
BitwardenBasicDialog(
title = dialog.title?.invoke(),
message = dialog.message(),
throwable = dialog.error,
onDismissRequest = handler.onDismissErrorDialog,
)
}
@@ -141,7 +142,8 @@ fun CompleteRegistrationScreen(
title = stringResource(
id = R.string.create_account
.takeIf { state.onboardingEnabled }
?: R.string.set_password),
?: R.string.set_password,
),
scrollBehavior = scrollBehavior,
navigationIcon = rememberVectorPainter(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),

View File

@@ -210,6 +210,7 @@ class CompleteRegistrationViewModel @Inject constructor(
title = R.string.an_error_has_occurred.asText(),
message = registerAccountResult.errorMessage?.asText()
?: R.string.generic_error_message.asText(),
error = registerAccountResult.error,
),
)
}
@@ -525,6 +526,7 @@ sealed class CompleteRegistrationDialog : Parcelable {
data class Error(
val title: Text?,
val message: Text,
val error: Throwable? = null,
) : CompleteRegistrationDialog()
}

View File

@@ -116,6 +116,7 @@ fun CreateAccountScreen(
BitwardenBasicDialog(
title = dialog.title?.invoke(),
message = dialog.message(),
throwable = dialog.error,
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(ErrorDialogDismiss) }
},

View File

@@ -180,6 +180,7 @@ class CreateAccountViewModel @Inject constructor(
title = R.string.an_error_has_occurred.asText(),
message = registerAccountResult.errorMessage?.asText()
?: R.string.generic_error_message.asText(),
error = registerAccountResult.error,
),
)
}
@@ -464,6 +465,7 @@ sealed class CreateAccountDialog : Parcelable {
data class Error(
val title: Text?,
val message: Text,
val error: Throwable? = null,
) : CreateAccountDialog()
}

View File

@@ -4276,7 +4276,13 @@ class AuthRepositoryTest {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
assertEquals(RegisterResult.Error(errorMessage = null), result)
assertEquals(
RegisterResult.Error(
errorMessage = null,
error = MissingPropertyException("Captcha ID"),
),
result,
)
}
@Test
@@ -4322,6 +4328,7 @@ class AuthRepositoryTest {
@Test
fun `register Failure should return Error with no message`() = runTest {
val error = RuntimeException()
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
coEvery {
identityService.register(
@@ -4339,7 +4346,7 @@ class AuthRepositoryTest {
kdfIterations = DEFAULT_KDF_ITERATIONS.toUInt(),
),
)
} returns RuntimeException().asFailure()
} returns error.asFailure()
val result = repository.register(
email = EMAIL,
@@ -4349,7 +4356,7 @@ class AuthRepositoryTest {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
assertEquals(RegisterResult.Error(errorMessage = null), result)
assertEquals(RegisterResult.Error(errorMessage = null, error = error), result)
}
@Test
@@ -4383,7 +4390,7 @@ class AuthRepositoryTest {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
assertEquals(RegisterResult.Error(errorMessage = "message"), result)
assertEquals(RegisterResult.Error(errorMessage = "message", error = null), result)
}
@Test
@@ -4420,7 +4427,7 @@ class AuthRepositoryTest {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
assertEquals(RegisterResult.Error(errorMessage = "expected"), result)
assertEquals(RegisterResult.Error(errorMessage = "expected", error = null), result)
}
@Test

View File

@@ -178,6 +178,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
@Test
fun `CallToActionClick register returns error should update errorDialogState`() = runTest {
val error = Throwable("Fail!")
coEvery {
mockAuthRepository.register(
email = EMAIL,
@@ -188,7 +189,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
} returns RegisterResult.Error(errorMessage = "mock_error")
} returns RegisterResult.Error(errorMessage = "mock_error", error = error)
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
viewModel.stateFlow.test {
assertEquals(VALID_INPUT_STATE, awaitItem())
@@ -202,6 +203,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
dialog = CompleteRegistrationDialog.Error(
title = R.string.an_error_has_occurred.asText(),
message = "mock_error".asText(),
error = error,
),
),
awaitItem(),
@@ -273,7 +275,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
} returns RegisterResult.Error(null)
} returns RegisterResult.Error(errorMessage = null, error = null)
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
viewModel.trySendAction(CompleteRegistrationAction.ContinueWithBreachedPasswordClick)
coVerify {

View File

@@ -252,6 +252,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
@Test
fun `SubmitClick register returns error should update errorDialogState`() = runTest {
val error = Throwable("Fail!")
val repo = mockk<AuthRepository> {
every { captchaTokenResultFlow } returns flowOf()
coEvery {
@@ -263,7 +264,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
} returns RegisterResult.Error(errorMessage = "mock_error")
} returns RegisterResult.Error(errorMessage = "mock_error", error = error)
}
val viewModel = CreateAccountViewModel(
savedStateHandle = validInputHandle,
@@ -281,6 +282,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
dialog = CreateAccountDialog.Error(
title = R.string.an_error_has_occurred.asText(),
message = "mock_error".asText(),
error = error,
),
),
awaitItem(),
@@ -368,7 +370,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
} returns RegisterResult.Error(null)
} returns RegisterResult.Error(errorMessage = null, error = null)
}
val viewModel = CreateAccountViewModel(
savedStateHandle = validInputHandle,