Skip to content

Commit

Permalink
Sign up basic functionallity
Browse files Browse the repository at this point in the history
  • Loading branch information
mitrejcevski committed Oct 2, 2024
1 parent 93d2ab5 commit 2e10631
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package nl.jovmit.androiddevs.domain.auth

import nl.jovmit.androiddevs.core.network.AuthService
import nl.jovmit.androiddevs.core.network.LoginData
import nl.jovmit.androiddevs.core.network.SignUpData
import nl.jovmit.androiddevs.domain.auth.data.AuthResult
import nl.jovmit.androiddevs.domain.auth.data.toDomain
import javax.inject.Inject
Expand All @@ -17,7 +16,7 @@ internal class RemoteAuthRepository @Inject constructor(
val response = authService.login(loginData)
AuthResult.Success(response.token, response.userData.toDomain())
} catch (exception: Exception) {
AuthResult.Error
AuthResult.ExistingUserError
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ sealed class AuthResult {
val user: User
) : AuthResult()

data object Error : AuthResult()
data object BackendError : AuthResult()

data object ExistingUserError : AuthResult()

data object OfflineError : AuthResult()
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract class AuthContractTest {

val result = usersCatalog.login(bob.email, bobPassword)

assertThat(result).isEqualTo(AuthResult.Error)
assertThat(result).isEqualTo(AuthResult.ExistingUserError)
}

@Test
Expand All @@ -39,7 +39,7 @@ abstract class AuthContractTest {

val result = usersCatalog.login(bob.email, bobPassword)

assertThat(result).isEqualTo(AuthResult.Error)
assertThat(result).isEqualTo(AuthResult.ExistingUserError)
}

abstract fun usersCatalogWith(authToken: String, password: String, users: List<User>): AuthRepository
Expand Down
2 changes: 1 addition & 1 deletion feature/signup/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ android {

dependencies {
implementation(projects.core.view)
implementation(projects.domain.auth)
implementation(libs.bundles.hilt)
testImplementation(project(":testutils"))

kapt(libs.hilt.compiler)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private fun SignUpScreenContent(
private fun PreviewLoginScreen() {
AppTheme {
SignUpScreenContent(
signUpScreenState = SignUpScreenState(),
signUpScreenState = SignUpScreenState(incorrectEmailFormat = true),
onEmailChanged = {},
onPasswordChanged = {},
onAboutChanged = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ package nl.jovmit.androiddevs.feature.signup

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import nl.jovmit.androiddevs.core.view.extensions.update
import nl.jovmit.androiddevs.core.view.validation.EmailValidator
import nl.jovmit.androiddevs.core.view.validation.PasswordValidator
import nl.jovmit.androiddevs.domain.auth.AuthRepository
import nl.jovmit.androiddevs.domain.auth.data.AuthResult
import nl.jovmit.androiddevs.feature.signup.state.SignUpScreenState

class SignUpViewModel(
private val savedStateHandle: SavedStateHandle
class SignUpViewModel (
private val savedStateHandle: SavedStateHandle,
private val authRepository: AuthRepository
) : ViewModel() {

private val emailValidator = EmailValidator()
private val passwordValidator = PasswordValidator()

val screenState = savedStateHandle.getStateFlow(SIGN_UP, SignUpScreenState())

fun updateEmail(value: String) {
Expand All @@ -29,6 +39,74 @@ class SignUpViewModel(
}
}

fun signUp() {
val email = screenState.value.email
val password = screenState.value.password
val about = screenState.value.about

val isEmailValid = emailValidator.validateEmail(email)
val isPasswordValid = passwordValidator.validatePassword(password)

if (!isEmailValid) { setIncorrectEmailFormatError() }
if (!isPasswordValid) { setIncorrectPasswordFormatError() }

if (isEmailValid && isPasswordValid) {
performSignUp(email, password, about)
}
}

private fun setIncorrectEmailFormatError() {
savedStateHandle.update<SignUpScreenState>(SIGN_UP) {
it.copy(incorrectEmailFormat = true)
}
}

private fun setIncorrectPasswordFormatError() {
savedStateHandle.update<SignUpScreenState>(SIGN_UP) {
it.copy(incorrectPasswordFormat = true)
}
}

private fun performSignUp(email: String, password: String, about: String) {
viewModelScope.launch { //TODO (we need to offload from main thread)
val result = authRepository.signUp(email, password, about)
onAuthResults(result)
}
}

private fun onAuthResults(result: AuthResult) {
when (result) {
is AuthResult.Success -> onSignedUp()
is AuthResult.BackendError -> onBackendError()
is AuthResult.ExistingUserError -> onExistingUserError()
is AuthResult.OfflineError -> onOfflineError()
}
}

private fun onSignedUp() {
savedStateHandle.update<SignUpScreenState>(SIGN_UP) {
it.copy(isSignedUp = true)
}
}

private fun onBackendError() {
savedStateHandle.update<SignUpScreenState>(SIGN_UP) {
it.copy(isBackendError = true)
}
}

private fun onExistingUserError() {
savedStateHandle.update<SignUpScreenState>(SIGN_UP) {
it.copy(isExistingEmail = true)
}
}

private fun onOfflineError() {
savedStateHandle.update<SignUpScreenState>(SIGN_UP) {
it.copy(isOfflineError = true)
}
}

companion object {
private const val SIGN_UP = "signUpKey"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@ import kotlinx.parcelize.Parcelize
data class SignUpScreenState(
val email: String = "",
val password: String = "",
val about: String = ""
val about: String = "",
val incorrectEmailFormat: Boolean = false,
val incorrectPasswordFormat: Boolean = false,
val isSignedUp: Boolean = false,
val isExistingEmail: Boolean = false,
val isBackendError: Boolean = false,
val isOfflineError: Boolean = false
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ package nl.jovmit.androiddevs.feature.signup
import androidx.lifecycle.SavedStateHandle
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import nl.jovmit.androiddevs.domain.auth.InMemoryAuthRepository
import nl.jovmit.androiddevs.feature.signup.state.SignUpScreenState
import org.junit.jupiter.api.Test

class SignUpScreenStateTest {

private val authRepository = InMemoryAuthRepository()
private val savedStateHandle = SavedStateHandle()

@Test
fun `email value updating`() {
val newEmailValue = "email@"
val viewModel = SignUpViewModel(savedStateHandle)
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.updateEmail(newEmailValue)

Expand All @@ -25,7 +27,7 @@ class SignUpScreenStateTest {
@Test
fun `password value updating`() = runTest {
val newValue = ":irrelevant:"
val viewModel = SignUpViewModel(savedStateHandle)
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.updatePassword(newValue)

Expand All @@ -37,7 +39,7 @@ class SignUpScreenStateTest {
@Test
fun `about value updating`() {
val newValue = ":dunno:"
val viewModel = SignUpViewModel(savedStateHandle)
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.updateAbout(newValue)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package nl.jovmit.androiddevs.feature.signup

import androidx.lifecycle.SavedStateHandle
import com.google.common.truth.Truth.assertThat
import nl.jovmit.androiddevs.domain.auth.InMemoryAuthRepository
import nl.jovmit.androiddevs.domain.auth.data.User
import nl.jovmit.androiddevs.feature.signup.state.SignUpScreenState
import nl.jovmit.androiddevs.testutils.CoroutineTestExtension
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(CoroutineTestExtension::class)
class SignUpTest {

// - we don't want long running operations in the main thread
// - state delivery in order
// - contract test to make sure prod code is aligned with the fake

private val validEmail = "[email protected]"
private val validPassword = "ValidP@ssword1"

private val authRepository = InMemoryAuthRepository()
private val savedStateHandle = SavedStateHandle()

@Test
fun invalidEmail() {
val email = "invalid email format"
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.signUp(email = email)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(
email = email,
incorrectEmailFormat = true,
incorrectPasswordFormat = true
)
)
}

@Test
fun invalidPassword() {
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.signUp(email = validEmail)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(email = validEmail, incorrectPasswordFormat = true)
)
}

@Test
fun invalidEmailWithValidPassword() {
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.signUp(password = validPassword)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(password = validPassword, incorrectEmailFormat = true)
)
}

@Test
fun signedUpSuccessfully() {
val viewModel = SignUpViewModel(savedStateHandle, authRepository)

viewModel.signUp(validEmail, validPassword)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(
email = validEmail,
password = validPassword,
isSignedUp = true
)
)
}

@Test
fun attemptToSignUpWithKnownEmail() {
val newPassword = "another$validPassword"
val repository = InMemoryAuthRepository(
usersForPassword = mapOf(validPassword to listOf(User("userId", validEmail, "")))
)
val viewModel = SignUpViewModel(savedStateHandle, repository)

viewModel.signUp(validEmail, newPassword)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(
email = validEmail,
password = newPassword,
isExistingEmail = true
)
)
}

@Test
fun errorCreatingNewAccount() {
val unavailableAuthRepository = InMemoryAuthRepository().apply {
setUnavailable()
}
val viewModel = SignUpViewModel(savedStateHandle, unavailableAuthRepository)

viewModel.signUp(validEmail, validPassword)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(
email = validEmail,
password = validPassword,
isBackendError = true
)
)
}

@Test
fun attemptToSignUpWhenOffline() {
val offlineAuthRepository = InMemoryAuthRepository().apply {
setOffline()
}
val viewModel = SignUpViewModel(savedStateHandle, offlineAuthRepository)

viewModel.signUp(validEmail, validPassword)

assertThat(viewModel.screenState.value).isEqualTo(
SignUpScreenState(
email = validEmail,
password = validPassword,
isOfflineError = true
)
)
}

private fun SignUpViewModel.signUp(email: String = "", password: String = "") {
updateEmail(email)
updatePassword(password)
signUp()
}
}
Loading

0 comments on commit 2e10631

Please sign in to comment.