Add ConfigurationApproval validation

This commit is contained in:
Wolf-Martell Montwé 2023-06-14 18:34:54 +02:00
parent 9f96ab6ee0
commit d04a91f566
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
12 changed files with 187 additions and 7 deletions

View file

@ -18,6 +18,10 @@ internal interface DomainContract {
fun execute(password: String): ValidationResult
}
fun interface ValidateConfigurationApproval {
fun execute(isApproved: Boolean?, isAutoDiscoveryTrusted: Boolean?): ValidationResult
}
fun interface ValidateServer {
fun execute(server: String): ValidationResult
}

View file

@ -0,0 +1,23 @@
package app.k9mail.feature.account.setup.domain.usecase
import app.k9mail.core.common.domain.usecase.validation.ValidationError
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
class ValidateConfigurationApproval : UseCase.ValidateConfigurationApproval {
override fun execute(isApproved: Boolean?, isAutoDiscoveryTrusted: Boolean?): ValidationResult {
return if (isApproved == null && isAutoDiscoveryTrusted == null) {
ValidationResult.Success
} else if (isAutoDiscoveryTrusted == true) {
ValidationResult.Success
} else if (isApproved == true) {
ValidationResult.Success
} else {
ValidationResult.Failure(ValidateConfigurationApprovalError.ApprovalRequired)
}
}
sealed interface ValidateConfigurationApprovalError : ValidationError {
object ApprovalRequired : ValidateConfigurationApprovalError
}
}

View file

@ -47,6 +47,7 @@ interface AccountAutoConfigContract {
interface Validator {
fun validateEmailAddress(emailAddress: String): ValidationResult
fun validatePassword(password: String): ValidationResult
fun validateConfigurationApproval(isApproved: Boolean?, isAutoDiscoveryTrusted: Boolean?): ValidationResult
}
sealed interface Error {

View file

@ -1,12 +1,15 @@
package app.k9mail.feature.account.setup.ui.autoconfig
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
import app.k9mail.feature.account.setup.domain.usecase.ValidateConfigurationApproval
import app.k9mail.feature.account.setup.domain.usecase.ValidateEmailAddress
import app.k9mail.feature.account.setup.domain.usecase.ValidatePassword
internal class AccountAutoConfigValidator(
private val emailAddressValidator: ValidateEmailAddress = ValidateEmailAddress(),
private val passwordValidator: ValidatePassword = ValidatePassword(),
private val emailAddressValidator: UseCase.ValidateEmailAddress = ValidateEmailAddress(),
private val passwordValidator: UseCase.ValidatePassword = ValidatePassword(),
private val configurationApprovalValidator: UseCase.ValidateConfigurationApproval = ValidateConfigurationApproval(),
) : AccountAutoConfigContract.Validator {
override fun validateEmailAddress(emailAddress: String): ValidationResult {
@ -16,4 +19,11 @@ internal class AccountAutoConfigValidator(
override fun validatePassword(password: String): ValidationResult {
return passwordValidator.execute(password)
}
override fun validateConfigurationApproval(
isApproved: Boolean?,
isAutoDiscoveryTrusted: Boolean?,
): ValidationResult {
return configurationApprovalValidator.execute(isApproved, isAutoDiscoveryTrusted)
}
}

View file

@ -149,13 +149,23 @@ internal class AccountAutoConfigViewModel(
with(state.value) {
val emailValidationResult = validator.validateEmailAddress(emailAddress.value)
val passwordValidationResult = validator.validatePassword(password.value)
val hasError = listOf(emailValidationResult, passwordValidationResult)
.any { it is ValidationResult.Failure }
val configurationApprovalValidationResult = validator.validateConfigurationApproval(
isApproved = configurationApproved.value,
isAutoDiscoveryTrusted = autoDiscoverySettings?.isTrusted,
)
val hasError = listOf(
emailValidationResult,
passwordValidationResult,
configurationApprovalValidationResult,
).any { it is ValidationResult.Failure }
updateState {
it.copy(
emailAddress = it.emailAddress.updateFromValidationResult(emailValidationResult),
password = it.password.updateFromValidationResult(passwordValidationResult),
configurationApproved = it.configurationApproved.updateFromValidationResult(
configurationApprovalValidationResult,
),
)
}

View file

@ -1,14 +1,17 @@
package app.k9mail.feature.account.setup.ui.autoconfig
import android.content.res.Resources
import app.k9mail.core.common.domain.usecase.validation.ValidationError
import app.k9mail.feature.account.setup.R
import app.k9mail.feature.account.setup.domain.entity.AutoDiscoveryConnectionSecurity
import app.k9mail.feature.account.setup.domain.usecase.ValidateConfigurationApproval
internal fun AutoDiscoveryConnectionSecurity.toResourceString(resources: Resources): String {
return when (this) {
AutoDiscoveryConnectionSecurity.StartTLS -> resources.getString(
R.string.account_setup_connection_security_start_tls,
)
AutoDiscoveryConnectionSecurity.TLS -> resources.getString(
R.string.account_setup_connection_security_ssl,
)
@ -21,3 +24,23 @@ internal fun AccountAutoConfigContract.Error.toResourceString(resources: Resourc
AccountAutoConfigContract.Error.UnknownError -> resources.getString(R.string.account_setup_error_unknown)
}
}
internal fun ValidationError.toResourceString(resources: Resources): String {
return when (this) {
is ValidateConfigurationApproval.ValidateConfigurationApprovalError -> toConfigurationApprovalErrorString(
resources,
)
else -> throw IllegalArgumentException("Unknown error: $this")
}
}
private fun ValidateConfigurationApproval.ValidateConfigurationApprovalError.toConfigurationApprovalErrorString(
resources: Resources,
): String {
return when (this) {
ValidateConfigurationApproval.ValidateConfigurationApprovalError.ApprovalRequired -> resources.getString(
R.string.account_setup_error_configuration_approval_required,
)
}
}

View file

@ -80,7 +80,7 @@ internal fun AutoDiscoveryServerSettingsView(
},
)
if (username != null) {
if (username.isNotEmpty()) {
ServerSettingRow(
icon = Icons.Filled.user,
text = buildAnnotatedString {

View file

@ -5,17 +5,21 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.k9mail.core.ui.compose.designsystem.molecule.input.CheckboxInput
import app.k9mail.core.ui.compose.theme.MainTheme
import app.k9mail.feature.account.setup.R
import app.k9mail.feature.account.setup.domain.input.BooleanInputField
import app.k9mail.feature.account.setup.ui.autoconfig.toResourceString
@Composable
internal fun ConfigurationApprovalView(
approvalState: BooleanInputField,
onConfigurationApprovalChange: (Boolean) -> Unit,
) {
val resources = LocalContext.current.resources
Spacer(modifier = Modifier.height(MainTheme.spacings.default))
CheckboxInput(
@ -24,7 +28,7 @@ internal fun ConfigurationApprovalView(
),
checked = approvalState.value ?: false,
onCheckedChange = onConfigurationApprovalChange,
errorMessage = approvalState.error?.toString(), // TODO
errorMessage = approvalState.error?.toResourceString(resources),
contentPadding = PaddingValues(),
)
}

View file

@ -23,7 +23,6 @@
<string name="account_setup_auto_config_loading_message">Finding email details</string>
<string name="account_setup_auto_config_loading_error">Failed to load email configuration</string>
<string name="account_setup_auto_config_status_header_title_configuration_found">Configuration Found</string>
<string name="account_setup_auto_config_status_header_title_configuration_not_found">Configuration Not Found</string>
<string name="account_setup_auto_config_status_header_subtitle_configuration_trusted">Configure automatically</string>
@ -32,6 +31,7 @@
<string name="account_setup_auto_config_status_header_subtitle_configuration_not_found">Configure manually</string>
<string name="account_setup_auto_config_status_edit_configuration_button_label">Edit configuration</string>
<string name="account_setup_auto_config_status_checkbox_configuration_untrusted_confirmation_label">I trust this configuration</string>
<string name="account_setup_error_configuration_approval_required">It is required to approve the configuration.</string>
<string name="account_setup_incoming_config_top_bar_title">Incoming server settings</string>
<string name="account_setup_incoming_config_protocol_type_label">Protocol</string>

View file

@ -0,0 +1,90 @@
package app.k9mail.feature.account.setup.domain.usecase
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
import assertk.assertThat
import assertk.assertions.isInstanceOf
import assertk.assertions.prop
import org.junit.Test
class ValidateConfigurationApprovalTest {
@Test
fun `should succeed when auto discovery is approved and trusted`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = true, isAutoDiscoveryTrusted = true)
assertThat(result).isInstanceOf(ValidationResult.Success::class)
}
@Test
fun `should succeed when auto discovery not approved but is trusted`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = false, isAutoDiscoveryTrusted = true)
assertThat(result).isInstanceOf(ValidationResult.Success::class)
}
@Test
fun `should succeed when auto discovery is approved but not trusted`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = true, isAutoDiscoveryTrusted = false)
assertThat(result).isInstanceOf(ValidationResult.Success::class)
}
@Test
fun `should fail when auto discovery is not approved and not trusted`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = false, isAutoDiscoveryTrusted = false)
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
.prop(ValidationResult.Failure::error)
.isInstanceOf(ValidateConfigurationApproval.ValidateConfigurationApprovalError.ApprovalRequired::class)
}
@Test
fun `should succeed when auto discovery isApproved null and is trusted`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = null, isAutoDiscoveryTrusted = true)
assertThat(result).isInstanceOf(ValidationResult.Success::class)
}
@Test
fun `should fail when auto discovery is isApproved null and is not trusted`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = null, isAutoDiscoveryTrusted = false)
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
.prop(ValidationResult.Failure::error)
.isInstanceOf(ValidateConfigurationApproval.ValidateConfigurationApprovalError.ApprovalRequired::class)
}
@Test
fun `should fail when auto discovery is approved and trusted is null`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = false, isAutoDiscoveryTrusted = null)
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
.prop(ValidationResult.Failure::error)
.isInstanceOf(ValidateConfigurationApproval.ValidateConfigurationApprovalError.ApprovalRequired::class)
}
@Test
fun `should fail when auto discovery is not approved and trusted is null`() {
val useCase = ValidateConfigurationApproval()
val result = useCase.execute(isApproved = false, isAutoDiscoveryTrusted = null)
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
.prop(ValidationResult.Failure::error)
.isInstanceOf(ValidateConfigurationApproval.ValidateConfigurationApprovalError.ApprovalRequired::class)
}
}

View file

@ -288,6 +288,11 @@ class AccountAutoConfigViewModelTest {
error = null,
isValid = true,
),
configurationApproved = BooleanInputField(
value = null,
error = null,
isValid = true,
),
),
)
@ -344,6 +349,11 @@ class AccountAutoConfigViewModelTest {
error = TestError,
isValid = false,
),
configurationApproved = BooleanInputField(
value = null,
error = null,
isValid = true,
),
),
)
}

View file

@ -5,7 +5,12 @@ import app.k9mail.core.common.domain.usecase.validation.ValidationResult
class FakeAccountAutoConfigValidator(
private val emailAddressAnswer: ValidationResult = ValidationResult.Success,
private val passwordAnswer: ValidationResult = ValidationResult.Success,
private val configurationApprovalAnswer: ValidationResult = ValidationResult.Success,
) : AccountAutoConfigContract.Validator {
override fun validateEmailAddress(emailAddress: String): ValidationResult = emailAddressAnswer
override fun validatePassword(password: String): ValidationResult = passwordAnswer
override fun validateConfigurationApproval(
isApproved: Boolean?,
isAutoDiscoveryTrusted: Boolean?,
): ValidationResult = configurationApprovalAnswer
}