diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e4655fc..cf231b5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,6 +50,12 @@ android { dependencies { + //Testes Unitarios e Instrumentados + testImplementation("io.mockk:mockk:1.13.10") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") + + //Room implementation(libs.room.runtime) implementation(libs.room.ktx) diff --git a/app/src/main/java/com/delecrode/devhub/App.kt b/app/src/main/java/com/delecrode/devhub/App.kt index feabef8..b311e88 100644 --- a/app/src/main/java/com/delecrode/devhub/App.kt +++ b/app/src/main/java/com/delecrode/devhub/App.kt @@ -1,7 +1,10 @@ package com.delecrode.devhub import android.app.Application -import com.delecrode.devhub.di.appModule +import com.delecrode.devhub.di.dataModule +import com.delecrode.devhub.di.repositoryModule +import com.delecrode.devhub.di.useCaseModule +import com.delecrode.devhub.di.viewModelModule import org.koin.android.ext.koin.androidContext import org.koin.core.context.GlobalContext.startKoin @@ -10,7 +13,10 @@ class App : Application() { super.onCreate() startKoin { androidContext(this@App) - modules(appModule) + modules( dataModule, + repositoryModule, + useCaseModule, + viewModelModule) } } } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/di/AppModule.kt b/app/src/main/java/com/delecrode/devhub/di/AppModule.kt deleted file mode 100644 index 62a871e..0000000 --- a/app/src/main/java/com/delecrode/devhub/di/AppModule.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.delecrode.devhub.di - -import androidx.room.Room -import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource -import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSourceImpl -import com.delecrode.devhub.data.local.database.AppDatabase -import com.delecrode.devhub.data.local.database.MIGRATION_1_2 -import com.delecrode.devhub.data.local.database.data.RepoLocalDataSource -import com.delecrode.devhub.data.local.database.data.RepoLocalDataSourceImpl -import com.delecrode.devhub.data.remote.firebase.UserExtraData -import com.delecrode.devhub.data.remote.webApi.instance.RetrofitInstance -import com.delecrode.devhub.data.repository.AuthRepositoryImpl -import com.delecrode.devhub.data.repository.RepoRepositoryImpl -import com.delecrode.devhub.data.repository.UserRepositoryImpl -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.RepoRepository -import com.delecrode.devhub.domain.repository.UserRepository -import com.delecrode.devhub.domain.session.SessionViewModel -import com.delecrode.devhub.presentation.ui.favoritos.RepoFavViewModel -import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel -import com.delecrode.devhub.presentation.ui.home.HomeViewModel -import com.delecrode.devhub.presentation.ui.login.AuthViewModel -import com.delecrode.devhub.presentation.ui.profile.ProfileViewModel -import com.delecrode.devhub.presentation.ui.register.RegisterViewModel -import com.delecrode.devhub.presentation.ui.repo.RepoDetailViewModel -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.firestore.FirebaseFirestore -import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module - - -val appModule = module { - - single { FirebaseAuth.getInstance() } - single { FirebaseFirestore.getInstance() } - - single { RetrofitInstance.userApi } - single { RetrofitInstance.repoApi } - - single { - Room.databaseBuilder( - androidContext(), - AppDatabase::class.java, - "app_database" - ) - .addMigrations(MIGRATION_1_2) - .build() - - } - - single { - get().repoDao() - } - - single { AuthLocalDataSourceImpl(get()) } - single { RepoLocalDataSourceImpl(get()) } - - single { com.delecrode.devhub.data.remote.firebase.FirebaseAuth(get()) } - single { UserExtraData(get()) } - - single { UserRepositoryImpl(get(), get(), get()) } - single { RepoRepositoryImpl(get(), get(), get()) } - single { AuthRepositoryImpl(get(), get(), get()) } - - viewModel { HomeViewModel(get(), get()) } - viewModel { RepoDetailViewModel(get()) } - viewModel { AuthViewModel(get()) } - viewModel { SessionViewModel(get()) } - viewModel { RegisterViewModel(get(),get()) } - viewModel { ProfileViewModel(get(), get()) } - viewModel { RepoFavViewModel(get()) } - viewModel { ForgotPasswordViewModel(get()) } -} \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/di/DataModule.kt b/app/src/main/java/com/delecrode/devhub/di/DataModule.kt new file mode 100644 index 0000000..c38e56c --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/DataModule.kt @@ -0,0 +1,42 @@ +package com.delecrode.devhub.di + +import androidx.room.Room +import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource +import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSourceImpl +import com.delecrode.devhub.data.local.database.AppDatabase +import com.delecrode.devhub.data.local.database.MIGRATION_1_2 +import com.delecrode.devhub.data.local.database.data.RepoLocalDataSource +import com.delecrode.devhub.data.local.database.data.RepoLocalDataSourceImpl +import com.delecrode.devhub.data.remote.firebase.UserExtraData +import com.delecrode.devhub.data.remote.webApi.instance.RetrofitInstance +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val dataModule = module { + + single { FirebaseAuth.getInstance() } + single { FirebaseFirestore.getInstance() } + + single { RetrofitInstance.userApi } + single { RetrofitInstance.repoApi } + + single { + Room.databaseBuilder( + androidContext(), + AppDatabase::class.java, + "app_database" + ) + .addMigrations(MIGRATION_1_2) + .build() + } + + single { get().repoDao() } + + single { AuthLocalDataSourceImpl(get()) } + single { RepoLocalDataSourceImpl(get()) } + + single { com.delecrode.devhub.data.remote.firebase.FirebaseAuth(get()) } + single { UserExtraData(get()) } +} diff --git a/app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt b/app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt new file mode 100644 index 0000000..5430e9e --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/RepositoryModule.kt @@ -0,0 +1,24 @@ +package com.delecrode.devhub.di + +import com.delecrode.devhub.data.repository.AuthRepositoryImpl +import com.delecrode.devhub.data.repository.RepoRepositoryImpl +import com.delecrode.devhub.data.repository.UserRepositoryImpl +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.domain.repository.RepoRepository +import com.delecrode.devhub.domain.repository.UserRepository +import org.koin.dsl.module + +val repositoryModule = module { + + single { + UserRepositoryImpl(get(), get(), get()) + } + + single { + RepoRepositoryImpl(get(), get(), get()) + } + + single { + AuthRepositoryImpl(get(), get(), get()) + } +} diff --git a/app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt b/app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt new file mode 100644 index 0000000..606e5ea --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/UseCaseModule.kt @@ -0,0 +1,11 @@ +package com.delecrode.devhub.di + +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import org.koin.dsl.module + +val useCaseModule = module { + + factory { FetchUserDataUseCase(get()) } + factory { AuthUseCase(get()) } +} diff --git a/app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt b/app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt new file mode 100644 index 0000000..58032bd --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/di/ViewModelModule.kt @@ -0,0 +1,24 @@ +package com.delecrode.devhub.di + +import HomeViewModel +import com.delecrode.devhub.domain.session.SessionViewModel +import com.delecrode.devhub.presentation.ui.favoritos.RepoFavViewModel +import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel +import com.delecrode.devhub.presentation.ui.login.AuthViewModel +import com.delecrode.devhub.presentation.ui.profile.ProfileViewModel +import com.delecrode.devhub.presentation.ui.register.RegisterViewModel +import com.delecrode.devhub.presentation.ui.repo.RepoDetailViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val viewModelModule = module { + + viewModel { HomeViewModel(get(), get()) } + viewModel { ProfileViewModel(get(), get()) } + viewModel { AuthViewModel(get()) } + viewModel { SessionViewModel(get()) } + viewModel { RegisterViewModel(get(), get()) } + viewModel { RepoDetailViewModel(get()) } + viewModel { RepoFavViewModel(get()) } + viewModel { ForgotPasswordViewModel(get()) } +} diff --git a/app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt b/app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt new file mode 100644 index 0000000..36a8344 --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/domain/useCase/AuthUseCase.kt @@ -0,0 +1,11 @@ +package com.delecrode.devhub.domain.useCase + +import com.delecrode.devhub.domain.repository.AuthRepository + +class AuthUseCase ( + private val authRepository: AuthRepository + +){ + suspend fun signOut() = + authRepository.signOut() +} \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt b/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt new file mode 100644 index 0000000..5446768 --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/domain/useCase/fetchUserDataUseCase.kt @@ -0,0 +1,16 @@ +package com.delecrode.devhub.domain.useCase + +import com.delecrode.devhub.domain.repository.UserRepository + +class FetchUserDataUseCase( + private val userRepository: UserRepository, +) { + suspend fun loadUserFromFirebase() = + userRepository.getUserForFirebase() + + suspend fun loadUserFromGit(username: String) = + userRepository.getUserForGitHub(username) + + suspend fun loadRepos(username: String) = + userRepository.getRepos(username) +} diff --git a/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt b/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt index a5759cd..11bd0a7 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/navigation/AppNavHost.kt @@ -1,5 +1,6 @@ package com.delecrode.devhub.presentation.navigation +import HomeViewModel import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.navigation.NavType @@ -13,7 +14,6 @@ import com.delecrode.devhub.presentation.ui.favoritos.ReposFavScreen import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordScreen import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel import com.delecrode.devhub.presentation.ui.home.HomeScreen -import com.delecrode.devhub.presentation.ui.home.HomeViewModel import com.delecrode.devhub.presentation.ui.login.AuthViewModel import com.delecrode.devhub.presentation.ui.login.LoginScreen import com.delecrode.devhub.presentation.ui.profile.ProfileScreen diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt index 27700a0..7073440 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordScreen.kt @@ -46,8 +46,8 @@ fun ForgotPasswordScreen(navController: NavController, viewModel: ForgotPassword var email by remember { mutableStateOf("") } - LaunchedEffect(state.success) { - if (state.success) { + LaunchedEffect(state.isSuccess) { + if (state.isSuccess) { navController.navigate(AppDestinations.Login.route){ popUpTo(AppDestinations.Login.route){ inclusive = true diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt index d4e4ece..3afbfb8 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordState.kt @@ -3,6 +3,6 @@ package com.delecrode.devhub.presentation.ui.forgot data class ForgotPasswordState( val isLoading: Boolean = false, val error: String? = null, - val success: Boolean = false, + val isSuccess: Boolean = false, val emailError: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt index e3213eb..ad4961a 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/forgot/ForgotPasswordViewModel.kt @@ -32,7 +32,7 @@ class ForgotPasswordViewModel(private val authRepository: AuthRepository) : View is Result.Success -> { _state.value = _state.value.copy( isLoading = false, - success = true + isSuccess = true ) } diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt index c7ca8d5..8e3e933 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeScreen.kt @@ -1,5 +1,6 @@ package com.delecrode.devhub.presentation.ui.home +import HomeViewModel import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -89,7 +90,7 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { } } LaunchedEffect(Unit) { - homeViewModel.getUserForFirebase() + homeViewModel.loadHome() } Scaffold( diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt index 93ed70f..699a896 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/home/HomeViewModel.kt @@ -1,163 +1,112 @@ -package com.delecrode.devhub.presentation.ui.home - -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import com.delecrode.devhub.presentation.ui.home.HomeState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch + class HomeViewModel( - private val userRepository: UserRepository, - private val authRepository: AuthRepository -) : - ViewModel() { + private val fetchUserData: FetchUserDataUseCase, + private val authUseCase: AuthUseCase +) : ViewModel() { private val _uiState = MutableStateFlow(HomeState()) val uiState: StateFlow = _uiState fun onSearchTextChange(value: String) { - _uiState.update { - it.copy(searchText = value) - } + _uiState.update { it.copy(searchText = value) } } fun onSearchClick() { val search = uiState.value.searchText if (search.isBlank()) return - getRepos(search) - getUserForSearchGit(search) + loadSearchUser(search) + loadRepos(search) } - - fun getUserForSearchGit(userName: String) { + fun loadSearchUser(username: String) { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getUserForGitHub(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForSearchGit = result.data, - isLoading = false, - error = null - ) - } - - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) - } + _uiState.update { it.copy(isLoading = true, error = null) } + + when (val result = fetchUserData.loadUserFromGit(username)) { + is Result.Success -> + _uiState.update { + it.copy(userForSearchGit = result.data, isLoading = false) + } + + is Result.Error -> + _uiState.update { + it.copy(error = result.message, isLoading = false) + } } } } - fun getUserForFirebase() { + fun loadHome() { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getUserForFirebase()) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForFirebase = result.data, - error = null - ) - getUserForGit(result.data.username) - } - - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) - } + val firebaseResult = loadUserFromFirebase() + if (firebaseResult is Result.Success) { + loadUserFromGit(firebaseResult.data.username) } + } + } + + suspend fun loadUserFromFirebase(): Result { + _uiState.update { it.copy(isLoading = true, error = null) } + val result = fetchUserData.loadUserFromFirebase() + when (result) { + is Result.Success -> _uiState.update { it.copy(userForFirebase = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } } + return result } - fun getUserForGit(userName: String) { - viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getUserForGitHub(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForGit = result.data, - isLoading = false, - error = null - ) - } + suspend fun loadUserFromGit(username: String): Result { + _uiState.update { it.copy(isLoading = true, error = null) } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) - } - } + val result = fetchUserData.loadUserFromGit(username) + when (result) { + is Result.Success -> _uiState.update { it.copy(userForGit = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } } + return result } - fun getRepos(userName: String) { + + fun loadRepos(username: String) { viewModelScope.launch { - _uiState.value = _uiState.value.copy( - isLoading = true, - error = null - ) - when (val result = userRepository.getRepos(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - repos = result.data, - isLoading = false, - error = null - ) - } + when (val result = fetchUserData.loadRepos(username)) { + is Result.Success -> + _uiState.update { it.copy(repos = result.data) } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = result.message - ) - } + is Result.Error -> + _uiState.update { it.copy(error = result.message) } } } } - fun signOut() { - viewModelScope.launch { - try { - authRepository.signOut() - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao fazer logout", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + fun signOut() { + viewModelScope.launch { + try { + authUseCase.signOut() + } catch (e: Exception) { + _uiState.update { + it.copy(error = e.message, isLoading = false) } } } - - fun clearStates() { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = null - ) - } + } fun clearUi() { _uiState.update { @@ -169,5 +118,14 @@ class HomeViewModel( ) } } -} + fun clearStates() { + _uiState.update { + it.copy( + userForFirebase = null, + userForGit = null, + userForSearchGit = null + ) + } + } +} diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt index d67b16c..c523147 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/login/AuthViewModel.kt @@ -43,7 +43,8 @@ class AuthViewModel( when (val result = repository.signIn(email, password)) { is Result.Success -> { - _state.value = AuthState(isSuccess = true) + _state.value = AuthState(isSuccess = true, userUid = result.data.uid) + } is Result.Error -> { @@ -79,7 +80,6 @@ class AuthViewModel( return when { password.isBlank() -> "Senha é obrigatória" password.length < 6 -> "Senha deve ter pelo menos 6 caracteres" - !countPassword(password) -> "Senha deve conter pelo menos 1 letra e 1 número" else -> null } } @@ -87,8 +87,4 @@ class AuthViewModel( private fun isValidEmailFormat(email: String): Boolean { return Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$").matches(email) } - - private fun countPassword(password: String): Boolean { - return password.length >= 6 - } } diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt index 5501df3..eee8d89 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileScreen.kt @@ -59,7 +59,7 @@ fun ProfileScreen(navController: NavController, viewModel: ProfileViewModel) { val repos = state.value.repos LaunchedEffect(Unit) { - viewModel.getUserForFirebase() + viewModel.loadProfile() } LaunchedEffect(state.value.error) { diff --git a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt index cabfb0d..6c134e4 100644 --- a/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/presentation/ui/profile/ProfileViewModel.kt @@ -1,100 +1,77 @@ package com.delecrode.devhub.presentation.ui.profile -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.delecrode.devhub.domain.repository.AuthRepository -import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class ProfileViewModel( - private val userRepository: UserRepository, - private val authRepository: AuthRepository + private val fetchUserData: FetchUserDataUseCase, + private val authUseCase: AuthUseCase ) : ViewModel() { private val _uiState = MutableStateFlow(ProfileState()) val uiState: StateFlow = _uiState - fun getUserForFirebase() { - _uiState.value = _uiState.value.copy( - isLoading = true - ) + fun loadProfile() { viewModelScope.launch { - when (val result = userRepository.getUserForFirebase()) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForFirebase = result.data, - isLoading = false - ) - getUserForGit(result.data.username) - } - - is Result.Error -> { - _uiState.value = _uiState.value.copy( - error = result.message, - isLoading = false - ) + _uiState.update { it.copy(isLoading = true, error = null) } + val firebaseResult = loadUserFromFirebase() + if (firebaseResult is Result.Success) { + val gitResult = loadUserFromGit(firebaseResult.data.username) + if (gitResult is Result.Success && gitResult.data.login != null) { + loadRepos(gitResult.data.login) } } } } - fun getUserForGit(userName: String) { - viewModelScope.launch { - when (val result = userRepository.getUserForGitHub(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - userForGit = result.data, - isLoading = false - ) - getRepos(userName) - } + suspend fun loadUserFromFirebase(): Result { + val result = fetchUserData.loadUserFromFirebase() + when (result) { + is Result.Success -> _uiState.update { it.copy(userForFirebase = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } + } + return result + } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - error = result.message, - isLoading = false - ) - } - } + suspend fun loadUserFromGit(username: String): Result { + val result = fetchUserData.loadUserFromGit(username) + when (result) { + is Result.Success -> _uiState.update { it.copy(userForGit = result.data, isLoading = false) } + is Result.Error -> _uiState.update { it.copy(error = result.message, isLoading = false) } } + return result } - fun getRepos(userName: String) { - viewModelScope.launch { - when (val result = userRepository.getRepos(userName)) { - is Result.Success -> { - _uiState.value = _uiState.value.copy( - repos = result.data, - isLoading = false - ) - } - is Result.Error -> { - _uiState.value = _uiState.value.copy( - error = result.message, - isLoading = false - ) - } - } + suspend fun loadRepos(username: String) { + when (val result = fetchUserData.loadRepos(username)) { + is Result.Success -> + _uiState.update { it.copy(repos = result.data, isLoading = false) } + + is Result.Error -> + _uiState.update { it.copy(error = result.message, isLoading = false) } } } fun signOut() { viewModelScope.launch { try { - authRepository.signOut() + authUseCase.signOut() } catch (e: Exception) { - Log.e("ProfileViewModel", "Erro ao fazer logout", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + _uiState.update { + it.copy(error = e.message, isLoading = false) + } } } } diff --git a/app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt b/app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt deleted file mode 100644 index 5d7d645..0000000 --- a/app/src/test/java/com/delecrode/devhub/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.delecrode.devhub - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt b/app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt new file mode 100644 index 0000000..6ce3475 --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/MainDispatcherRule.kt @@ -0,0 +1,24 @@ +package com.delecrode.devhub + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@OptIn(ExperimentalCoroutinesApi::class) +class MainDispatcherRule( + private val dispatcher: TestDispatcher = StandardTestDispatcher() +) : TestWatcher() { + + override fun starting(description: Description) { + Dispatchers.setMain(dispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/AuthTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/AuthTestes.kt new file mode 100644 index 0000000..7345f20 --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/AuthTestes.kt @@ -0,0 +1,155 @@ +package com.delecrode.devhub.viewModel + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserAuth +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.presentation.ui.login.AuthViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class AuthTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private lateinit var viewModel: AuthViewModel + + @Before + fun setup() { + viewModel = AuthViewModel(authRepository) + } + @Test + fun `quando signIn sucesso deve atualizar state com uid e sucesso`() = runTest { + val userAuth = UserAuth( + uid = "uid_123", + email = "teste@email.com" + ) + + coEvery { + authRepository.signIn(any(), any()) + } returns Result.Success(userAuth) + + viewModel.signIn("teste@email.com", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(state.isSuccess) + assert(state.userUid == "uid_123") + assert(!state.isLoading) + assert(state.error == null) + + coVerify(exactly = 1) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando email invalido na validacao local deve atualizar state com erro no email`() = + runTest { + + viewModel.signIn("teste", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.emailError == "Email inválido") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando email vazio na validacao local deve atualizar state com erro no email`() = + runTest { + + viewModel.signIn("", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.emailError == "Email é obrigatório") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando senha invalida na validacao local deve atualizar state com erro na senha`() = + runTest { + viewModel.signIn("teste@gmail.com", "teste") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.passwordError == "Senha deve ter pelo menos 6 caracteres") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando senha vazia na validacao local deve atualizar state com erro na senha`() = + runTest { + viewModel.signIn("teste@gmail.com", "") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.userUid == null) + assert(state.passwordError == "Senha é obrigatória") + + coVerify(exactly = 0) { + authRepository.signIn(any(), any()) + } + } + + @Test + fun `quando signIn falhar deve atualizar state erro`() = runTest { + coEvery { + authRepository.signIn(any(), any()) + } returns Result.Error("Credenciais invalidas") + + viewModel.signIn("teste@email.com", "Teste123") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(state.userUid == null) + assert(!state.isLoading) + assert(state.error == "Credenciais invalidas") + + coVerify(exactly = 1) { + authRepository.signIn(any(), any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt new file mode 100644 index 0000000..41c5e53 --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/ForgotTestes.kt @@ -0,0 +1,108 @@ +package com.delecrode.devhub.viewModel + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.presentation.ui.forgot.ForgotPasswordViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class ForgotTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private lateinit var viewModel: ForgotPasswordViewModel + + @Before + fun setup() { + viewModel = ForgotPasswordViewModel(authRepository) + } + + @Test + fun `quando forgotPassword sucesso deve atualizar state com sucesso`() = runTest { + + coEvery { + authRepository.forgotPassword("teste@email.com") + } returns Result.Success(Unit) + + viewModel.forgotPassword("teste@email.com") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(state.isSuccess) + assert(!state.isLoading) + assert(state.error == null) + + coVerify(exactly = 1) { + authRepository.forgotPassword(any()) + } + } + + @Test + fun `quando forgotPassword falhar deve atualizar state erro`() = runTest{ + coEvery { + authRepository.forgotPassword("teste@email.com") + } returns Result.Error("Erro ao enviar e-mail") + + viewModel.forgotPassword("teste@email.com") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.error == "Erro ao enviar e-mail") + + coVerify(exactly = 1) { + authRepository.forgotPassword(any()) + } + } + + @Test + fun `quando email invalido na validacao local deve atualizar state com erro no email`() = runTest{ + viewModel.forgotPassword("teste") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.error == null) + assert(state.emailError == "Email inválido") + + coVerify(exactly = 0) { + authRepository.forgotPassword(any()) + } + } + + @Test + fun `quando email vazio na validacao local deve atualizar state com erro no email`() = runTest{ + viewModel.forgotPassword("") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isSuccess) + assert(!state.isLoading) + assert(state.error == null) + assert(state.emailError == "Email é obrigatório") + + coVerify(exactly = 0) { + authRepository.forgotPassword(any()) + } + + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/HomeTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/HomeTestes.kt new file mode 100644 index 0000000..bfa3b6e --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/HomeTestes.kt @@ -0,0 +1,259 @@ +package com.delecrode.devhub.viewModel + +import HomeViewModel +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.Repos +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class HomeTestes { + @get:Rule + val dispatcherRule = MainDispatcherRule() + + val fetchUserData: FetchUserDataUseCase = mockk() + val authUseCase: AuthUseCase = mockk() + + private lateinit var homeViewModel: HomeViewModel + + @Before + fun setup() { + homeViewModel = HomeViewModel(fetchUserData, authUseCase) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + val userForFirebase = UserForFirebase( + fullName = "teste full name", + username = "teste username", + email = "teste email" + ) + + val repos = listOf( + Repos( + id = 1, + node_id = "teste node id", + name = "teste name", + full_name = "teste full name", + private = false, + description = "teste description", + url = "teste url", + created_at = "teste created at", + updated_at = "teste updated at", + pushed_at = "teste pushed at", + clone_url = "teste clone url", + ) + ) + + @Test + fun `quando buscar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Success(userForGit) + + homeViewModel.loadUserFromGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + + @Test + fun `quando buscar um usuario do GitHub falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadSearchUser("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e retornar sucesso`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + homeViewModel.loadUserFromFirebase() + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadHome() + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForFirebase == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do git e falhar`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadHome() + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + } + } + + @Test + fun `quando buscar os repositorios do usuario deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Success(repos) + + homeViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Error("Erro ao buscar repositorios") + + homeViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } + + + @Test + fun `quando buscar um usuario atraves da pesquisa deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Success(userForGit) + + homeViewModel.loadSearchUser("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForSearchGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario atraves da pesquisa falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Error("Erro ao buscar usuario") + + homeViewModel.loadUserFromGit("teste userName") + + advanceUntilIdle() + + val state = homeViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForSearchGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/ProfileTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/ProfileTestes.kt new file mode 100644 index 0000000..e1bd36e --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/ProfileTestes.kt @@ -0,0 +1,283 @@ +package com.delecrode.devhub.viewModel + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.Repos +import com.delecrode.devhub.domain.model.UserForFirebase +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.useCase.AuthUseCase +import com.delecrode.devhub.domain.useCase.FetchUserDataUseCase +import com.delecrode.devhub.presentation.ui.profile.ProfileViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class ProfileTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + val fetchUserData: FetchUserDataUseCase = mockk() + val authUseCase: AuthUseCase = mockk() + + private lateinit var profileViewModel: ProfileViewModel + + @Before + fun setup() { + profileViewModel = ProfileViewModel(fetchUserData, authUseCase) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + val userForFirebase = UserForFirebase( + fullName = "teste full name", + username = "teste username", + email = "teste email" + ) + + val repos = listOf( + Repos( + id = 1, + node_id = "teste node id", + name = "teste name", + full_name = "teste full name", + private = false, + description = "teste description", + url = "teste url", + created_at = "teste created at", + updated_at = "teste updated at", + pushed_at = "teste pushed at", + clone_url = "teste clone url", + ) + ) + + @Test + fun `quando buscar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("teste userName") + } returns Result.Success(userForGit) + + profileViewModel.loadUserFromGit("teste userName") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == userForGit) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario do GitHub deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromGit("") + } returns Result.Error("Erro ao buscar usuario") + + profileViewModel.loadUserFromGit("") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromGit(any()) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + profileViewModel.loadUserFromFirebase() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Error("Erro ao buscar usuario") + + profileViewModel.loadUserFromFirebase() + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.userForFirebase == UserForFirebase()) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do Git deve retornar sucesso e buscar os dados do repositorios deve retornar sucesso`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Success(userForGit) + + coEvery { + fetchUserData.loadRepos(userForGit.login.let { "teste userName" }) + } returns Result.Success(repos) + + + profileViewModel.loadProfile() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == userForGit) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + fetchUserData.loadRepos(any()) + } + } + + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do Git deve retornar erro`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Error("Erro ao buscar usuario") + + profileViewModel.loadProfile() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == null) + assert(state.error == "Erro ao buscar usuario") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + } + } + + @Test + fun `quando buscar um usuario do Firebase deve retornar sucesso e buscar dados do Git deve retornar sucesso e buscar os dados do repositorios deve retornar erro`() = + runTest { + coEvery { + fetchUserData.loadUserFromFirebase() + } returns Result.Success(userForFirebase) + + coEvery { + fetchUserData.loadUserFromGit(userForFirebase.username) + } returns Result.Success(userForGit) + + coEvery { + fetchUserData.loadRepos(userForGit.login.let { "teste userName" }) + } returns Result.Error("Erro ao buscar repositorios") + + + profileViewModel.loadProfile() + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(state.userForFirebase == userForFirebase) + assert(state.userForGit == userForGit) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + fetchUserData.loadUserFromFirebase() + fetchUserData.loadUserFromGit(userForFirebase.username) + fetchUserData.loadRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario deve retornar sucesso`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Success(repos) + + profileViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos == repos) + assert(state.error == null) + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } + + @Test + fun `quando buscar os repositorios do usuario falhar deve retornar erro`() = runTest { + coEvery { + fetchUserData.loadRepos("teste userName") + } returns Result.Error("Erro ao buscar repositorios") + + profileViewModel.loadRepos("teste userName") + + advanceUntilIdle() + + val state = profileViewModel.uiState.value + + assert(!state.isLoading) + assert(state.repos.isEmpty()) + assert(state.error == "Erro ao buscar repositorios") + + coVerify(exactly = 1) { + fetchUserData.loadRepos(any()) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt b/app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt new file mode 100644 index 0000000..e94611a --- /dev/null +++ b/app/src/test/java/com/delecrode/devhub/viewModel/RegisterTestes.kt @@ -0,0 +1,100 @@ +package com.delecrode.devhub.viewModel + +import com.delecrode.devhub.MainDispatcherRule +import com.delecrode.devhub.data.utils.Result +import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.domain.repository.UserRepository +import com.delecrode.devhub.presentation.ui.register.RegisterViewModel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class RegisterTestes { + + @get:Rule + val dispatcherRule = MainDispatcherRule() + + private val authRepository: AuthRepository = mockk() + private val userRepository: UserRepository = mockk() + private lateinit var viewModel: RegisterViewModel + + @Before + fun setup() { + viewModel = RegisterViewModel(authRepository, userRepository) + } + + val userForGit = UserForGit( + login = "teste userName", + avatar_url = "teste avatar", + url = "teste url", + name = "teste nome", + bio = "teste bio", + repos_url = "teste repos url" + ) + + @Test + fun `quando validar um usuario do GitHub deve retornar sucesso`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste userName") + } returns Result.Success(userForGit) + + viewModel.validateGithubUsername("teste userName") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isLoading) + assert(state.usernameSuccess) + assert(state.usernameError == null) + assert(state.error == null) + + coVerify(exactly = 1) { + userRepository.getUserForGitHub("teste userName") + } + } + + @Test + fun `quando validar um usuario que não existe no GitHub deve retornar erro`() = runTest { + coEvery { + userRepository.getUserForGitHub("teste de erro") + } returns Result.Error("Usuário não existe no GitHub") + + viewModel.validateGithubUsername("teste de erro") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isLoading) + assert(!state.isSuccess) + assert(state.usernameError == "Usuário não existe no GitHub") + + coVerify(exactly = 1) { + userRepository.getUserForGitHub(any()) + } + } + + @Test + fun `quando validar um usuario vazio no GitHub deve retornar erro`() = runTest { + viewModel.validateGithubUsername("") + + advanceUntilIdle() + + val state = viewModel.state.value + + assert(!state.isLoading) + assert(!state.isSuccess) + assert(state.usernameError == "Username é obrigatório") + + coVerify(exactly = 0) { + userRepository.getUserForGitHub(any()) + } + } +} \ No newline at end of file