diff --git a/frontend/app/src/test/java/com/example/speechbuddy/domain/SessionManagerTest.kt b/frontend/app/src/test/java/com/example/speechbuddy/domain/SessionManagerTest.kt index 59dcc6b9..6b406fd0 100644 --- a/frontend/app/src/test/java/com/example/speechbuddy/domain/SessionManagerTest.kt +++ b/frontend/app/src/test/java/com/example/speechbuddy/domain/SessionManagerTest.kt @@ -1,120 +1,120 @@ -//package com.example.speechbuddy.domain -// -//import androidx.arch.core.executor.testing.InstantTaskExecutorRule -//import androidx.lifecycle.Observer -//import com.example.speechbuddy.domain.models.AuthToken -//import com.example.speechbuddy.utils.Constants.Companion.GUEST_ID -//import io.mockk.* -//import junit.framework.TestCase.assertEquals -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.ExperimentalCoroutinesApi -//import kotlinx.coroutines.test.TestCoroutineDispatcher -//import kotlinx.coroutines.test.resetMain -//import kotlinx.coroutines.test.runBlockingTest -//import kotlinx.coroutines.test.setMain -//import org.junit.After -//import org.junit.Before -//import org.junit.Rule -//import org.junit.Test -// -//@ExperimentalCoroutinesApi -//class SessionManagerTest { -// -// @get:Rule -// val rule = InstantTaskExecutorRule() -// -// private val testCoroutineDispatcher = TestCoroutineDispatcher() -// -// private lateinit var authTokenObserver: Observer -// private lateinit var userIdObserver: Observer -// private lateinit var isLoginObserver: Observer -// -// private lateinit var sessionManager: SessionManager -// -// @Before -// fun setup() { -// Dispatchers.setMain(testCoroutineDispatcher) -// -// authTokenObserver = mockk(relaxed = true) -// userIdObserver = mockk(relaxed = true) -// isLoginObserver = mockk(relaxed = true) -// -// sessionManager = SessionManager() -// sessionManager.cachedToken.observeForever(authTokenObserver) -// sessionManager.userId.observeForever(userIdObserver) -// sessionManager.isLogin.observeForever(isLoginObserver) -// } -// -// @After -// fun tearDown() { -// Dispatchers.resetMain() -// testCoroutineDispatcher.cleanupTestCoroutines() -// clearAllMocks() -// } -// -// @Test -// fun `setAuthToken should update cachedToken LiveData`() = testCoroutineDispatcher.runBlockingTest { -// val testAuthToken = AuthToken("testAccessToken", "testRefreshToken") -// -// sessionManager.setAuthToken(testAuthToken) -// -// verify { authTokenObserver.onChanged(testAuthToken) } -// } -// -// @Test -// fun `setUserId should update userId LiveData`() = testCoroutineDispatcher.runBlockingTest { -// val testUserId = 123 -// -// sessionManager.setUserId(testUserId) -// -// verify { userIdObserver.onChanged(testUserId) } -// } -// -// @Test -// fun `deleteToken should set cachedToken and userId to null`() = testCoroutineDispatcher.runBlockingTest { -// sessionManager.deleteToken() -// -// verify { authTokenObserver.onChanged(null) } -// verify { userIdObserver.onChanged(null) } -// } -// -// @Test -// fun `enterGuestMode should set userId to GUEST_ID`() = testCoroutineDispatcher.runBlockingTest { -// sessionManager.enterGuestMode() -// -// verify { userIdObserver.onChanged(GUEST_ID) } -// } -// -// @Test -// fun `exitGuestMode should set userId to null`() = testCoroutineDispatcher.runBlockingTest { -// sessionManager.exitGuestMode() -// -// verify { userIdObserver.onChanged(null) } -// } -// -// @Test -// fun `setIsLogin should update isLogin LiveData`() = testCoroutineDispatcher.runBlockingTest { -// sessionManager.setIsLogin(true) -// -// verify { isLoginObserver.onChanged(true) } -// } -// -// @Test -// fun `isAuthorized should reflect correct authorization status`() = -// testCoroutineDispatcher.runBlockingTest { -// val testAuthToken = AuthToken("testAccessToken", "testRefreshToken") -// -// sessionManager.setUserId(456) -// sessionManager.setAuthToken(testAuthToken) -// -// verify { userIdObserver.onChanged(any()) } -// verify { authTokenObserver.onChanged(any()) } -// -// val isAuthorizedObserver = slot() -// every { isLoginObserver.onChanged(capture(isAuthorizedObserver)) } just Runs -// -// sessionManager.isAuthorized.observeForever(isLoginObserver) -// -// assertEquals(true, isAuthorizedObserver.captured) -// } -//} +package com.example.speechbuddy.domain + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer +import com.example.speechbuddy.domain.models.AuthToken +import com.example.speechbuddy.utils.Constants.Companion.GUEST_ID +import io.mockk.* +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@ExperimentalCoroutinesApi +class SessionManagerTest { + + @get:Rule + val rule = InstantTaskExecutorRule() + + private val testCoroutineDispatcher = TestCoroutineDispatcher() + + private lateinit var authTokenObserver: Observer + private lateinit var userIdObserver: Observer + private lateinit var isLoginObserver: Observer + + private lateinit var sessionManager: SessionManager + + @Before + fun setup() { + Dispatchers.setMain(testCoroutineDispatcher) + + authTokenObserver = mockk(relaxed = true) + userIdObserver = mockk(relaxed = true) + isLoginObserver = mockk(relaxed = true) + + sessionManager = SessionManager() + sessionManager.cachedToken.observeForever(authTokenObserver) + sessionManager.userId.observeForever(userIdObserver) + sessionManager.isLogin.observeForever(isLoginObserver) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + testCoroutineDispatcher.cleanupTestCoroutines() + clearAllMocks() + } + + @Test + fun `setAuthToken should update cachedToken LiveData`() = testCoroutineDispatcher.runBlockingTest { + val testAuthToken = AuthToken("testAccessToken", "testRefreshToken") + + sessionManager.setAuthToken(testAuthToken) + + verify { authTokenObserver.onChanged(testAuthToken) } + } + + @Test + fun `setUserId should update userId LiveData`() = testCoroutineDispatcher.runBlockingTest { + val testUserId = 123 + + sessionManager.setUserId(testUserId) + + verify { userIdObserver.onChanged(testUserId) } + } + + @Test + fun `deleteToken should set cachedToken and userId to null`() = testCoroutineDispatcher.runBlockingTest { + sessionManager.deleteToken() + + verify { authTokenObserver.onChanged(null) } + verify { userIdObserver.onChanged(null) } + } + + @Test + fun `enterGuestMode should set userId to GUEST_ID`() = testCoroutineDispatcher.runBlockingTest { + sessionManager.enterGuestMode() + + verify { userIdObserver.onChanged(GUEST_ID) } + } + + @Test + fun `exitGuestMode should set userId to null`() = testCoroutineDispatcher.runBlockingTest { + sessionManager.exitGuestMode() + + verify { userIdObserver.onChanged(null) } + } + + @Test + fun `setIsLogin should update isLogin LiveData`() = testCoroutineDispatcher.runBlockingTest { + sessionManager.setIsLogin(true) + + verify { isLoginObserver.onChanged(true) } + } + + @Test + fun `isAuthorized should reflect correct authorization status`() = + testCoroutineDispatcher.runBlockingTest { + val testAuthToken = AuthToken("testAccessToken", "testRefreshToken") + + sessionManager.setUserId(456) + sessionManager.setAuthToken(testAuthToken) + + verify { userIdObserver.onChanged(any()) } + verify { authTokenObserver.onChanged(any()) } + + val isAuthorizedObserver = slot() + every { isLoginObserver.onChanged(capture(isAuthorizedObserver)) } just Runs + + sessionManager.isAuthorized.observeForever(isLoginObserver) + + assertEquals(true, isAuthorizedObserver.captured) + } +} diff --git a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModelTest.kt b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModelTest.kt index 4ca81afa..e1fb5532 100644 --- a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModelTest.kt +++ b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModelTest.kt @@ -1,205 +1,205 @@ -//package com.example.speechbuddy.viewmodel -// -//import androidx.arch.core.executor.testing.InstantTaskExecutorRule -//import com.example.speechbuddy.domain.SessionManager -//import com.example.speechbuddy.repository.AuthRepository -//import com.example.speechbuddy.repository.SettingsRepository -//import com.example.speechbuddy.repository.SymbolRepository -//import com.example.speechbuddy.repository.UserRepository -//import com.example.speechbuddy.repository.WeightTableRepository -//import com.example.speechbuddy.ui.models.AccountSettingsAlert -//import com.example.speechbuddy.ui.models.AccountSettingsUiState -//import io.mockk.coEvery -//import io.mockk.coVerify -//import io.mockk.impl.annotations.MockK -//import io.mockk.mockk -//import kotlinx.coroutines.DelicateCoroutinesApi -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.flow.flowOf -//import kotlinx.coroutines.flow.take -//import kotlinx.coroutines.flow.toList -//import kotlinx.coroutines.newSingleThreadContext -//import kotlinx.coroutines.runBlocking -//import kotlinx.coroutines.test.resetMain -//import kotlinx.coroutines.test.setMain -//import org.junit.After -//import org.junit.Assert.assertEquals -//import org.junit.Before -//import org.junit.Rule -//import org.junit.Test -//import retrofit2.Response -//import java.time.LocalDate -// -//class AccountSettingsViewModelTest { -// @OptIn(DelicateCoroutinesApi::class) -// private val mainThreadSurrogate = newSingleThreadContext("UI thread") -// -// @MockK -// private val mockAuthRepository: AuthRepository = mockk(relaxed = true) -// private val mockSettingsRepository: SettingsRepository = mockk(relaxed = true) -// private val mockWeightTableRepository: WeightTableRepository = mockk(relaxed = true) -// private val mockSymbolRepository: SymbolRepository = mockk(relaxed = true) -// private val mockUserRepository: UserRepository = mockk(relaxed = true) -// private val mockSessionManager: SessionManager = mockk(relaxed = true) -// -// private lateinit var viewModel: AccountSettingsViewModel -// -// @get:Rule -// val instantExecutorRule = InstantTaskExecutorRule() -// -// @Before -// fun setUp() { -// Dispatchers.setMain(mainThreadSurrogate) -// -// viewModel = AccountSettingsViewModel( -// authRepository = mockAuthRepository, -// settingsRepository = mockSettingsRepository, -// weightTableRepository = mockWeightTableRepository, -// symbolRepository = mockSymbolRepository, -// userRepository = mockUserRepository, -// sessionManager = mockSessionManager -// ) -// } -// -// @After -// fun tearDown() { -// Dispatchers.resetMain() -// mainThreadSurrogate.close() -// } -// -// @Test -// fun `should show alert when called`() = runBlocking { -// var alert = AccountSettingsAlert.BACKUP -// var expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// var actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// -// alert = AccountSettingsAlert.LOADING -// expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// -// alert = AccountSettingsAlert.BACKUP_SUCCESS -// expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// -// alert = AccountSettingsAlert.LOGOUT -// expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// -// alert = AccountSettingsAlert.WITHDRAW -// expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// -// alert = AccountSettingsAlert.WITHDRAW_PROCEED -// expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// -// alert = AccountSettingsAlert.CONNECTION -// expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) -// viewModel.showAlert(alert) -// actualUiState = viewModel.uiState.value -// assertEquals(expectedUiState, actualUiState) -// } -// -// @Test -// fun `should hideAlert when called`() = runBlocking { -// viewModel.hideAlert() -// -// val expectedUiState = AccountSettingsUiState(alert = null) -// val actualUiState = viewModel.uiState.value -// -// assertEquals(expectedUiState, actualUiState) -// } -// -// @Test -// fun `should call authRepository when logout is called`() = runBlocking { -// coEvery { mockAuthRepository.logout() } returns flowOf(Response.success(null)) -// -// viewModel.logout() -// -// val observedUiStates = viewModel.uiState.take(2).toList() -// val expectedValue1 = -// AccountSettingsUiState(alert = AccountSettingsAlert.LOADING, buttonEnabled = false) -// val expectedValue2 = AccountSettingsUiState(alert = null, buttonEnabled = true) -// coVerify { mockAuthRepository.logout() } -// assertEquals(listOf(expectedValue1, expectedValue2), observedUiStates) -// -// } -// -// @Test -// fun `should call userRepository when withdraw is called`() = runBlocking { -// coEvery { mockAuthRepository.withdraw() } returns flowOf(Response.success(null)) -// -// viewModel.withdraw() -// -// val observedUiStates = viewModel.uiState.take(2).toList() -// val expectedValue1 = -// AccountSettingsUiState(alert = AccountSettingsAlert.LOADING, buttonEnabled = false) -// val expectedValue2 = AccountSettingsUiState(alert = null, buttonEnabled = true) -// coVerify { mockAuthRepository.withdraw() } -// assertEquals(listOf(expectedValue1, expectedValue2), observedUiStates) -// } -// -// @Test -// fun `should call authRepository when exitGuestMode is called`() = runBlocking { -// coEvery { mockSettingsRepository.resetSettings() } returns Unit -// coEvery { mockWeightTableRepository.resetAllWeightRows() } returns Unit -// coEvery { mockSymbolRepository.resetSymbolsAndFavorites() } returns Unit -// coEvery { mockUserRepository.deleteUserInfo() } returns Unit -// coEvery { mockSessionManager.exitGuestMode() } returns Unit -// -// viewModel.exitGuestMode() -// -// coVerify { mockSettingsRepository.resetSettings() } -// coVerify { mockWeightTableRepository.resetAllWeightRows() } -// coVerify { mockSymbolRepository.resetSymbolsAndFavorites() } -// coVerify { mockUserRepository.deleteUserInfo() } -// coVerify { mockSessionManager.exitGuestMode() } -// } -// -// @Test -// fun `should updates uiState and calls settingsRepository when backup is called`() = -// runBlocking { -// val date = LocalDate.now().toString() -// coEvery { mockSettingsRepository.displayBackup() } returns flowOf(Response.success(null)) -// coEvery { mockSettingsRepository.symbolListBackup() } returns flowOf( -// Response.success( -// null -// ) -// ) -// coEvery { mockSettingsRepository.favoriteSymbolBackup() } returns flowOf( -// Response.success( -// null -// ) -// ) -// coEvery { mockSettingsRepository.weightTableBackup() } returns flowOf( -// Response.success( -// null -// ) -// ) -// -// viewModel.backup() -// -// val observedUiStates = viewModel.uiState.take(2).toList() -// val expectedValue1 = -// AccountSettingsUiState(alert = AccountSettingsAlert.LOADING, buttonEnabled = false) -// val expectedValue2 = AccountSettingsUiState( -// alert = AccountSettingsAlert.BACKUP_SUCCESS, -// buttonEnabled = false -// ) -// coVerify { mockSettingsRepository.setLastBackupDate(date) } -// assertEquals(listOf(expectedValue1, expectedValue2), observedUiStates) -// -// } -//} \ No newline at end of file +package com.example.speechbuddy.viewmodel + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.example.speechbuddy.domain.SessionManager +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.repository.SettingsRepository +import com.example.speechbuddy.repository.SymbolRepository +import com.example.speechbuddy.repository.UserRepository +import com.example.speechbuddy.repository.WeightTableRepository +import com.example.speechbuddy.ui.models.AccountSettingsAlert +import com.example.speechbuddy.ui.models.AccountSettingsUiState +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import retrofit2.Response +import java.time.LocalDate + +class AccountSettingsViewModelTest { + @OptIn(DelicateCoroutinesApi::class) + private val mainThreadSurrogate = newSingleThreadContext("UI thread") + + @MockK + private val mockAuthRepository: AuthRepository = mockk(relaxed = true) + private val mockSettingsRepository: SettingsRepository = mockk(relaxed = true) + private val mockWeightTableRepository: WeightTableRepository = mockk(relaxed = true) + private val mockSymbolRepository: SymbolRepository = mockk(relaxed = true) + private val mockUserRepository: UserRepository = mockk(relaxed = true) + private val mockSessionManager: SessionManager = mockk(relaxed = true) + + private lateinit var viewModel: AccountSettingsViewModel + + @get:Rule + val instantExecutorRule = InstantTaskExecutorRule() + + @Before + fun setUp() { + Dispatchers.setMain(mainThreadSurrogate) + + viewModel = AccountSettingsViewModel( + authRepository = mockAuthRepository, + settingsRepository = mockSettingsRepository, + weightTableRepository = mockWeightTableRepository, + symbolRepository = mockSymbolRepository, + userRepository = mockUserRepository, + sessionManager = mockSessionManager + ) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + mainThreadSurrogate.close() + } + + @Test + fun `should show alert when called`() = runBlocking { + var alert = AccountSettingsAlert.BACKUP + var expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + var actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + + alert = AccountSettingsAlert.LOADING + expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + + alert = AccountSettingsAlert.BACKUP_SUCCESS + expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + + alert = AccountSettingsAlert.LOGOUT + expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + + alert = AccountSettingsAlert.WITHDRAW + expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + + alert = AccountSettingsAlert.WITHDRAW_PROCEED + expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + + alert = AccountSettingsAlert.CONNECTION + expectedUiState = AccountSettingsUiState(alert = alert, buttonEnabled = false) + viewModel.showAlert(alert) + actualUiState = viewModel.uiState.value + assertEquals(expectedUiState, actualUiState) + } + + @Test + fun `should hideAlert when called`() = runBlocking { + viewModel.hideAlert() + + val expectedUiState = AccountSettingsUiState(alert = null) + val actualUiState = viewModel.uiState.value + + assertEquals(expectedUiState, actualUiState) + } + + @Test + fun `should call authRepository when logout is called`() = runBlocking { + coEvery { mockAuthRepository.logout() } returns flowOf(Response.success(null)) + + viewModel.logout() + + val observedUiStates = viewModel.uiState.take(2).toList() + val expectedValue1 = + AccountSettingsUiState(alert = AccountSettingsAlert.LOADING, buttonEnabled = false) + val expectedValue2 = AccountSettingsUiState(alert = null, buttonEnabled = true) + coVerify { mockAuthRepository.logout() } + assertEquals(listOf(expectedValue1, expectedValue2), observedUiStates) + + } + + @Test + fun `should call userRepository when withdraw is called`() = runBlocking { + coEvery { mockAuthRepository.withdraw() } returns flowOf(Response.success(null)) + + viewModel.withdraw() + + val observedUiStates = viewModel.uiState.take(2).toList() + val expectedValue1 = + AccountSettingsUiState(alert = AccountSettingsAlert.LOADING, buttonEnabled = false) + val expectedValue2 = AccountSettingsUiState(alert = null, buttonEnabled = true) + coVerify { mockAuthRepository.withdraw() } + assertEquals(listOf(expectedValue1, expectedValue2), observedUiStates) + } + + @Test + fun `should call authRepository when exitGuestMode is called`() = runBlocking { + coEvery { mockSettingsRepository.resetSettings() } returns Unit + coEvery { mockWeightTableRepository.resetAllWeightRows() } returns Unit + coEvery { mockSymbolRepository.resetSymbolsAndFavorites() } returns Unit + coEvery { mockUserRepository.deleteUserInfo() } returns Unit + coEvery { mockSessionManager.exitGuestMode() } returns Unit + + viewModel.exitGuestMode() + + coVerify { mockSettingsRepository.resetSettings() } + coVerify { mockWeightTableRepository.resetAllWeightRows() } + coVerify { mockSymbolRepository.resetSymbolsAndFavorites() } + coVerify { mockUserRepository.deleteUserInfo() } + coVerify { mockSessionManager.exitGuestMode() } + } + + @Test + fun `should updates uiState and calls settingsRepository when backup is called`() = + runBlocking { + val date = LocalDate.now().toString() + coEvery { mockSettingsRepository.displayBackup() } returns flowOf(Response.success(null)) + coEvery { mockSettingsRepository.symbolListBackup() } returns flowOf( + Response.success( + null + ) + ) + coEvery { mockSettingsRepository.favoriteSymbolBackup() } returns flowOf( + Response.success( + null + ) + ) + coEvery { mockSettingsRepository.weightTableBackup() } returns flowOf( + Response.success( + null + ) + ) + + viewModel.backup() + + val observedUiStates = viewModel.uiState.take(2).toList() + val expectedValue1 = + AccountSettingsUiState(alert = AccountSettingsAlert.LOADING, buttonEnabled = false) + val expectedValue2 = AccountSettingsUiState( + alert = AccountSettingsAlert.BACKUP_SUCCESS, + buttonEnabled = false + ) + coVerify { mockSettingsRepository.setLastBackupDate(date) } + assertEquals(listOf(expectedValue1, expectedValue2), observedUiStates) + + } +} \ No newline at end of file diff --git a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/LoginViewModelTest.kt b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/LoginViewModelTest.kt index e82b5345..2bfa9cc8 100644 --- a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/LoginViewModelTest.kt +++ b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/LoginViewModelTest.kt @@ -1,445 +1,445 @@ -//package com.example.speechbuddy.viewmodel -// -//import androidx.arch.core.executor.testing.InstantTaskExecutorRule -//import com.example.speechbuddy.R -//import com.example.speechbuddy.data.remote.requests.AuthLoginRequest -//import com.example.speechbuddy.domain.SessionManager -//import com.example.speechbuddy.domain.models.AuthToken -//import com.example.speechbuddy.repository.AuthRepository -//import com.example.speechbuddy.repository.SettingsRepository -//import com.example.speechbuddy.repository.UserRepository -//import com.example.speechbuddy.ui.models.LoginErrorType -//import com.example.speechbuddy.utils.Resource -//import io.mockk.coEvery -//import io.mockk.coVerify -//import io.mockk.impl.annotations.MockK -//import io.mockk.mockk -//import junit.framework.TestCase.assertEquals -//import kotlinx.coroutines.DelicateCoroutinesApi -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.ExperimentalCoroutinesApi -//import kotlinx.coroutines.flow.flowOf -//import kotlinx.coroutines.newSingleThreadContext -//import kotlinx.coroutines.runBlocking -//import kotlinx.coroutines.test.resetMain -//import kotlinx.coroutines.test.setMain -//import org.junit.After -//import org.junit.Before -//import org.junit.Rule -//import org.junit.Test -// -//class LoginViewModelTest { -// -// @OptIn(DelicateCoroutinesApi::class) -// private val mainThreadSurrogate = newSingleThreadContext("UI thread") -// -// @MockK -// private val mockAuthRepository: AuthRepository = mockk() -// private val mockUserRepository: UserRepository = mockk() -// private val mockSettingsRepository: SettingsRepository = mockk() -// private val mockSessionManager: SessionManager = mockk() -// private lateinit var viewModel: LoginViewModel -// -// // boundary condition: 8 characters in password field -// private val validPassword = "password" -// private val shortPassword = "pwd" -// private val wrongPassword = "wrong_password" // valid format -// private val validEmail = "valid@test.com" -// private val invalidEmail = "invalid" -// private val unregisteredEmail = "unregistered@test.com" // valid format -// -// @get:Rule -// val instantExecutorRule = InstantTaskExecutorRule() -// -// @OptIn(ExperimentalCoroutinesApi::class) -// @Before -// fun setup() { -// Dispatchers.setMain(mainThreadSurrogate) -// viewModel = LoginViewModel( -// mockAuthRepository, -// mockUserRepository, -// mockSettingsRepository, -// mockSessionManager -// ) -// } -// -// @OptIn(ExperimentalCoroutinesApi::class) -// @After -// fun tearDown() { -// Dispatchers.resetMain() -// mainThreadSurrogate.close() -// } -// -// @Test -// fun `should set invalid email before login click when set email is called with invalid email`() { -// viewModel.setEmail(invalidEmail) -// -// assertEquals(invalidEmail, viewModel.emailInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set valid email before login click when set email is called with valid email`() { -// viewModel.setEmail(validEmail) -// -// assertEquals(validEmail, viewModel.emailInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set error type email after login click when set email is called with invalid email`() { -// viewModel.setEmail(invalidEmail) -// -// viewModel.login() -// -// assertEquals(invalidEmail, viewModel.emailInput) -// assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set error type null after login click when invalid email is changed to valid email`() { -// viewModel.setEmail(invalidEmail) -// -// viewModel.login() -// -// viewModel.setEmail(validEmail) -// -// assertEquals(validEmail, viewModel.emailInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(true, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set error type email after login click when set email is called with unregistered email`() { -// viewModel.setEmail(unregisteredEmail) -// viewModel.setPassword(validPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(unregisteredEmail, validPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_email\":[\"wrong email address\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// assertEquals(unregisteredEmail, viewModel.emailInput) -// assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set error type null after login click when unregistered email is changed to valid email`() { -// viewModel.setEmail(unregisteredEmail) -// viewModel.setPassword(validPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(unregisteredEmail, validPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_email\":[\"wrong email address\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// viewModel.setEmail(validEmail) -// -// assertEquals(validEmail, viewModel.emailInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(null, viewModel.uiState.value.error?.messageId) -// assertEquals(true, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set error type not email after login click when set email is called with valid email`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(wrongPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// assertEquals(validEmail, viewModel.emailInput) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set error type not email after login click when valid email is changed to invalid email`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(wrongPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// viewModel.setEmail(invalidEmail) -// -// assertEquals(invalidEmail, viewModel.emailInput) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// } -// -// @Test -// fun `should set short password before login click when set password is called with short email`() { -// viewModel.setPassword(shortPassword) -// -// assertEquals(shortPassword, viewModel.passwordInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// } -// -// @Test -// fun `should set valid password before login click when set password is called with valid email`() { -// viewModel.setPassword(validPassword) -// -// assertEquals(validPassword, viewModel.passwordInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// } -// -// @Test -// fun `should set error type password after login click when set password is called with short password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(shortPassword) -// -// viewModel.login() -// -// assertEquals(shortPassword, viewModel.passwordInput) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// } -// -// @Test -// fun `should set error type null after login click when short password is changed to valid password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(shortPassword) -// -// viewModel.login() -// -// viewModel.setPassword(validPassword) -// -// assertEquals(validPassword, viewModel.passwordInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(true, viewModel.uiState.value.isValidPassword) -// } -// -// @Test -// fun `should set error type password after login click when set password is called with wrong password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(wrongPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// assertEquals(wrongPassword, viewModel.passwordInput) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// } -// -// @Test -// fun `should set error type null after login click when wrong password is changed to valid password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(wrongPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// viewModel.setPassword(validPassword) -// -// assertEquals(validPassword, viewModel.passwordInput) -// assertEquals(null, viewModel.uiState.value.error?.type) -// assertEquals(true, viewModel.uiState.value.isValidPassword) -// } -// -// @Test -// fun `should set error type email when login is called with invalid email, short password`() { -// viewModel.setEmail(invalidEmail) -// viewModel.setPassword(shortPassword) -// -// viewModel.login() -// -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) -// } -// -// @Test -// fun `should set error type email when login is called with invalid email, valid password`() { -// viewModel.setEmail(invalidEmail) -// viewModel.setPassword(validPassword) -// -// viewModel.login() -// -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) -// } -// -// @Test -// fun `should set error type email when login is called with unregistered email, short password`() { -// viewModel.setEmail(unregisteredEmail) -// viewModel.setPassword(shortPassword) -// -// viewModel.login() -// -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) -// } -// -// @Test -// fun `should set error type email when login is called with unregistered email, valid password`() { -// viewModel.setEmail(unregisteredEmail) -// viewModel.setPassword(validPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(unregisteredEmail, validPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_email\":[\"wrong email address\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) -// } -// -// @Test -// fun `should set error type password when login is called with valid email, short password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(shortPassword) -// -// viewModel.login() -// -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) -// } -// -// @Test -// fun `should set error type password when login is called with valid email, wrong password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(wrongPassword) -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) -// } returns flowOf( -// Resource.error( -// "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", -// null -// ) -// ) -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// assertEquals(false, viewModel.uiState.value.isValidEmail) -// assertEquals(false, viewModel.uiState.value.isValidPassword) -// assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) -// assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) -// } -// -// @Test -// fun `should login success when login is called with valid email, valid password`() { -// viewModel.setEmail(validEmail) -// viewModel.setPassword(validPassword) -// -// val authToken = AuthToken("access", "refresh") -// -// coEvery { -// mockAuthRepository.login(AuthLoginRequest(validEmail, validPassword)) -// } returns flowOf( -// Resource.success(authToken) -// ) -// coEvery { mockSessionManager.setAuthToken(authToken) } returns Unit -// coEvery { mockSessionManager.cachedToken.value } returns AuthToken("access", "refresh") -// -// viewModel.login() -// Thread.sleep(10) //viewModel.login() does not immediately produce result -// -// assertEquals(authToken, mockSessionManager.cachedToken.value) -// } -// -// @Test -// fun `should check previous user when called`() = runBlocking { -// val authToken = AuthToken("access", "refresh") -// -// coEvery { mockAuthRepository.checkPreviousUser() } returns flowOf(Resource.success(Pair(1, authToken))) -// coEvery { mockSessionManager.setUserId(1) } returns Unit -// coEvery { mockSessionManager.setAuthToken(authToken) } returns Unit -// -// viewModel.checkPreviousUser() -// -// coVerify { mockAuthRepository.checkPreviousUser() } -// coVerify { mockSessionManager.setUserId(1) } -// coVerify { mockSessionManager.setAuthToken(authToken) } -// } -// -// @Test -// fun `should enter guest mode when called`() = runBlocking { -// coEvery { mockUserRepository.setGuestMode() } returns Unit -// coEvery { mockSessionManager.enterGuestMode() } returns Unit -// -// viewModel.enterGuestMode() -// -// coVerify { mockUserRepository.setGuestMode() } -// coVerify { mockSessionManager.enterGuestMode() } -// } -//} \ No newline at end of file +package com.example.speechbuddy.viewmodel + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.example.speechbuddy.R +import com.example.speechbuddy.data.remote.requests.AuthLoginRequest +import com.example.speechbuddy.domain.SessionManager +import com.example.speechbuddy.domain.models.AuthToken +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.repository.SettingsRepository +import com.example.speechbuddy.repository.UserRepository +import com.example.speechbuddy.ui.models.LoginErrorType +import com.example.speechbuddy.utils.Resource +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class LoginViewModelTest { + + @OptIn(DelicateCoroutinesApi::class) + private val mainThreadSurrogate = newSingleThreadContext("UI thread") + + @MockK + private val mockAuthRepository: AuthRepository = mockk() + private val mockUserRepository: UserRepository = mockk() + private val mockSettingsRepository: SettingsRepository = mockk() + private val mockSessionManager: SessionManager = mockk() + private lateinit var viewModel: LoginViewModel + + // boundary condition: 8 characters in password field + private val validPassword = "password" + private val shortPassword = "pwd" + private val wrongPassword = "wrong_password" // valid format + private val validEmail = "valid@test.com" + private val invalidEmail = "invalid" + private val unregisteredEmail = "unregistered@test.com" // valid format + + @get:Rule + val instantExecutorRule = InstantTaskExecutorRule() + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setup() { + Dispatchers.setMain(mainThreadSurrogate) + viewModel = LoginViewModel( + mockAuthRepository, + mockUserRepository, + mockSettingsRepository, + mockSessionManager + ) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @After + fun tearDown() { + Dispatchers.resetMain() + mainThreadSurrogate.close() + } + + @Test + fun `should set invalid email before login click when set email is called with invalid email`() { + viewModel.setEmail(invalidEmail) + + assertEquals(invalidEmail, viewModel.emailInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set valid email before login click when set email is called with valid email`() { + viewModel.setEmail(validEmail) + + assertEquals(validEmail, viewModel.emailInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set error type email after login click when set email is called with invalid email`() { + viewModel.setEmail(invalidEmail) + + viewModel.login() + + assertEquals(invalidEmail, viewModel.emailInput) + assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set error type null after login click when invalid email is changed to valid email`() { + viewModel.setEmail(invalidEmail) + + viewModel.login() + + viewModel.setEmail(validEmail) + + assertEquals(validEmail, viewModel.emailInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(true, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set error type email after login click when set email is called with unregistered email`() { + viewModel.setEmail(unregisteredEmail) + viewModel.setPassword(validPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(unregisteredEmail, validPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_email\":[\"wrong email address\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + assertEquals(unregisteredEmail, viewModel.emailInput) + assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) + assertEquals(false, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set error type null after login click when unregistered email is changed to valid email`() { + viewModel.setEmail(unregisteredEmail) + viewModel.setPassword(validPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(unregisteredEmail, validPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_email\":[\"wrong email address\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + viewModel.setEmail(validEmail) + + assertEquals(validEmail, viewModel.emailInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(null, viewModel.uiState.value.error?.messageId) + assertEquals(true, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set error type not email after login click when set email is called with valid email`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(wrongPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + assertEquals(validEmail, viewModel.emailInput) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) + assertEquals(false, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set error type not email after login click when valid email is changed to invalid email`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(wrongPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + viewModel.setEmail(invalidEmail) + + assertEquals(invalidEmail, viewModel.emailInput) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) + assertEquals(false, viewModel.uiState.value.isValidEmail) + } + + @Test + fun `should set short password before login click when set password is called with short email`() { + viewModel.setPassword(shortPassword) + + assertEquals(shortPassword, viewModel.passwordInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidPassword) + } + + @Test + fun `should set valid password before login click when set password is called with valid email`() { + viewModel.setPassword(validPassword) + + assertEquals(validPassword, viewModel.passwordInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidPassword) + } + + @Test + fun `should set error type password after login click when set password is called with short password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(shortPassword) + + viewModel.login() + + assertEquals(shortPassword, viewModel.passwordInput) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidPassword) + } + + @Test + fun `should set error type null after login click when short password is changed to valid password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(shortPassword) + + viewModel.login() + + viewModel.setPassword(validPassword) + + assertEquals(validPassword, viewModel.passwordInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(true, viewModel.uiState.value.isValidPassword) + } + + @Test + fun `should set error type password after login click when set password is called with wrong password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(wrongPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + assertEquals(wrongPassword, viewModel.passwordInput) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(false, viewModel.uiState.value.isValidPassword) + } + + @Test + fun `should set error type null after login click when wrong password is changed to valid password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(wrongPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + viewModel.setPassword(validPassword) + + assertEquals(validPassword, viewModel.passwordInput) + assertEquals(null, viewModel.uiState.value.error?.type) + assertEquals(true, viewModel.uiState.value.isValidPassword) + } + + @Test + fun `should set error type email when login is called with invalid email, short password`() { + viewModel.setEmail(invalidEmail) + viewModel.setPassword(shortPassword) + + viewModel.login() + + assertEquals(false, viewModel.uiState.value.isValidEmail) + assertEquals(false, viewModel.uiState.value.isValidPassword) + assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) + } + + @Test + fun `should set error type email when login is called with invalid email, valid password`() { + viewModel.setEmail(invalidEmail) + viewModel.setPassword(validPassword) + + viewModel.login() + + assertEquals(false, viewModel.uiState.value.isValidEmail) + assertEquals(false, viewModel.uiState.value.isValidPassword) + assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) + } + + @Test + fun `should set error type email when login is called with unregistered email, short password`() { + viewModel.setEmail(unregisteredEmail) + viewModel.setPassword(shortPassword) + + viewModel.login() + + assertEquals(false, viewModel.uiState.value.isValidEmail) + assertEquals(false, viewModel.uiState.value.isValidPassword) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) + } + + @Test + fun `should set error type email when login is called with unregistered email, valid password`() { + viewModel.setEmail(unregisteredEmail) + viewModel.setPassword(validPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(unregisteredEmail, validPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_email\":[\"wrong email address\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + assertEquals(false, viewModel.uiState.value.isValidEmail) + assertEquals(false, viewModel.uiState.value.isValidPassword) + assertEquals(LoginErrorType.EMAIL, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_email, viewModel.uiState.value.error?.messageId) + } + + @Test + fun `should set error type password when login is called with valid email, short password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(shortPassword) + + viewModel.login() + + assertEquals(false, viewModel.uiState.value.isValidEmail) + assertEquals(false, viewModel.uiState.value.isValidPassword) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) + } + + @Test + fun `should set error type password when login is called with valid email, wrong password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(wrongPassword) + + coEvery { + mockAuthRepository.login(AuthLoginRequest(validEmail, wrongPassword)) + } returns flowOf( + Resource.error( + "{\"code\":400,\"message\":{\"wrong_password\":[\"wrong password\"]}}", + null + ) + ) + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + assertEquals(false, viewModel.uiState.value.isValidEmail) + assertEquals(false, viewModel.uiState.value.isValidPassword) + assertEquals(LoginErrorType.PASSWORD, viewModel.uiState.value.error?.type) + assertEquals(R.string.wrong_password, viewModel.uiState.value.error?.messageId) + } + + @Test + fun `should login success when login is called with valid email, valid password`() { + viewModel.setEmail(validEmail) + viewModel.setPassword(validPassword) + + val authToken = AuthToken("access", "refresh") + + coEvery { + mockAuthRepository.login(AuthLoginRequest(validEmail, validPassword)) + } returns flowOf( + Resource.success(authToken) + ) + coEvery { mockSessionManager.setAuthToken(authToken) } returns Unit + coEvery { mockSessionManager.cachedToken.value } returns AuthToken("access", "refresh") + + viewModel.login() + Thread.sleep(10) //viewModel.login() does not immediately produce result + + assertEquals(authToken, mockSessionManager.cachedToken.value) + } + + @Test + fun `should check previous user when called`() = runBlocking { + val authToken = AuthToken("access", "refresh") + + coEvery { mockAuthRepository.checkPreviousUser() } returns flowOf(Resource.success(Pair(1, authToken))) + coEvery { mockSessionManager.setUserId(1) } returns Unit + coEvery { mockSessionManager.setAuthToken(authToken) } returns Unit + + viewModel.checkPreviousUser() + + coVerify { mockAuthRepository.checkPreviousUser() } + coVerify { mockSessionManager.setUserId(1) } + coVerify { mockSessionManager.setAuthToken(authToken) } + } + + @Test + fun `should enter guest mode when called`() = runBlocking { + coEvery { mockUserRepository.setGuestMode() } returns Unit + coEvery { mockSessionManager.enterGuestMode() } returns Unit + + viewModel.enterGuestMode() + + coVerify { mockUserRepository.setGuestMode() } + coVerify { mockSessionManager.enterGuestMode() } + } +} \ No newline at end of file diff --git a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModelTest.kt b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModelTest.kt index cf550695..88cb572a 100644 --- a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModelTest.kt +++ b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModelTest.kt @@ -1,202 +1,202 @@ -//package com.example.speechbuddy.viewmodel -// -//import androidx.arch.core.executor.testing.InstantTaskExecutorRule -//import com.example.speechbuddy.R -//import com.example.speechbuddy.data.remote.requests.AuthResetPasswordRequest -//import com.example.speechbuddy.domain.SessionManager -//import com.example.speechbuddy.repository.AuthRepository -//import com.example.speechbuddy.ui.models.ResetPasswordErrorType -//import io.mockk.coEvery -//import io.mockk.coVerify -//import io.mockk.impl.annotations.MockK -//import io.mockk.mockk -//import junit.framework.TestCase.assertEquals -//import kotlinx.coroutines.DelicateCoroutinesApi -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.ExperimentalCoroutinesApi -//import kotlinx.coroutines.flow.flowOf -//import kotlinx.coroutines.newSingleThreadContext -//import kotlinx.coroutines.test.resetMain -//import kotlinx.coroutines.test.setMain -//import okhttp3.ResponseBody.Companion.toResponseBody -//import org.junit.After -//import org.junit.Before -//import org.junit.Rule -//import org.junit.Test -//import retrofit2.Response -// -//class ResetPasswordViewModelTest { -// @OptIn(DelicateCoroutinesApi::class) -// private val mainThreadSurrogate = newSingleThreadContext("UI thread") -// -// @MockK -// private val repository: AuthRepository = mockk() -// private val sessionManager: SessionManager = mockk() -// private lateinit var viewModel: ResetPasswordViewModel -// -// private val validPassword = "123456789" //length >= 8 -// private val invalidPassword = "1234567" //length < 8 -// private val emptyPassword = "" -// private val passwordList = arrayListOf(validPassword, invalidPassword, emptyPassword) -// private val invalidPasswordList = arrayListOf(invalidPassword, emptyPassword) -// -// @get:Rule -// val instantExecutorRule = InstantTaskExecutorRule() -// -// @OptIn(ExperimentalCoroutinesApi::class) -// @Before -// fun setup() { -// Dispatchers.setMain(mainThreadSurrogate) -// viewModel = ResetPasswordViewModel(repository, sessionManager) -// } -// -// @OptIn(ExperimentalCoroutinesApi::class) -// @After -// fun tearDown() { -// Dispatchers.resetMain() -// mainThreadSurrogate.close() -// } -// -// @Test -// fun `should return no error when password is empty before click`() { -// passwordList.forEach { -// viewModel.setPassword(emptyPassword) -// viewModel.setPasswordCheck(it) -// assertEquals(viewModel.passwordInput, emptyPassword) -// assertEquals(viewModel.passwordCheckInput, it) -// assertEquals(viewModel.uiState.value.error?.type, null) -// assertEquals(viewModel.uiState.value.isValidPassword, false) -// } -// } -// -// @Test -// fun `should return no error when password is valid before click`() { -// passwordList.forEach { -// viewModel.setPassword(validPassword) -// viewModel.setPasswordCheck(it) -// assertEquals(viewModel.passwordInput, validPassword) -// assertEquals(viewModel.passwordCheckInput, it) -// assertEquals(viewModel.uiState.value.error?.type, null) -// assertEquals(viewModel.uiState.value.isValidPassword, false) -// } -// } -// -// @Test -// fun `should return no error when password is invalid before click`() { -// passwordList.forEach { -// viewModel.setPassword(invalidPassword) -// viewModel.setPasswordCheck(it) -// assertEquals(viewModel.passwordInput, invalidPassword) -// assertEquals(viewModel.passwordCheckInput, it) -// assertEquals(viewModel.uiState.value.error?.type, null) -// assertEquals(viewModel.uiState.value.isValidPassword, false) -// } -// } -// -// @Test -// fun `should set error type PASSWORD when password is empty`() { -// val onSuccess: () -> Unit = mockk(relaxed = true) -// passwordList.forEach { -// viewModel.setPassword(emptyPassword) -// viewModel.setPasswordCheck(it) -// viewModel.resetPassword(onSuccess) -// -// assertEquals(viewModel.passwordInput, emptyPassword) -// assertEquals(viewModel.passwordCheckInput, it) -// assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD) -// assertEquals(viewModel.uiState.value.error?.messageId, R.string.no_password) -// assertEquals(viewModel.uiState.value.isValidPassword, false) -// -// viewModel.setPassword(validPassword) -// assertEquals(viewModel.uiState.value.error?.type, null) -// assertEquals(viewModel.uiState.value.isValidPassword, true) -// } -// } //covers both cases when password equals password check, or not -// -// @Test -// fun `should set error type PASSWORD when password is invalid`() { -// val onSuccess: () -> Unit = mockk(relaxed = true) -// passwordList.forEach { -// viewModel.setPassword(invalidPassword) -// viewModel.setPasswordCheck(it) -// viewModel.resetPassword(onSuccess) -// -// assertEquals(viewModel.passwordInput, invalidPassword) -// assertEquals(viewModel.passwordCheckInput, it) -// assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD) -// assertEquals(viewModel.uiState.value.error?.messageId, R.string.password_too_short) -// assertEquals(viewModel.uiState.value.isValidPassword, false) -// -// viewModel.setPassword(validPassword) -// assertEquals(viewModel.uiState.value.error?.type, null) -// assertEquals(viewModel.uiState.value.isValidPassword, true) -// //checks whether validatePassword works well when changing the password into a valid one -// } -// } //covers both cases when password equals password check, or not -// -// @Test -// fun `should set error type PASSWORD_CHECK when password is valid`() { -// val onSuccess: () -> Unit = mockk(relaxed = true) -// invalidPasswordList.forEach { -// viewModel.setPassword(validPassword) -// viewModel.setPasswordCheck(it) -// viewModel.resetPassword(onSuccess) -// -// assertEquals(viewModel.passwordInput, validPassword) -// assertEquals(viewModel.passwordCheckInput, it) -// assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD_CHECK) -// assertEquals(viewModel.uiState.value.error?.messageId, R.string.wrong_password_check) -// assertEquals(viewModel.uiState.value.isValidPassword, false) -// //isValidPassword doesn't turn true when password was valid from the start -// } -// } -// -// @Test -// fun `should success when password is valid and equals password check`() { -// val onSuccess: () -> Unit = mockk(relaxed = true) -// viewModel.setPassword(validPassword) -// viewModel.setPasswordCheck(validPassword) -// coEvery { -// repository.resetPassword(AuthResetPasswordRequest(validPassword)) -// } returns flowOf(Response.success(null)) -// viewModel.resetPassword(onSuccess) -// Thread.sleep(10) -// -// assertEquals(viewModel.uiState.value.error?.type, null) -// coVerify { sessionManager.deleteToken() } -// } -// -// @Test -// fun `should fail when bad request occurs`() { -// val onSuccess: () -> Unit = mockk(relaxed = true) -// val errorResponseBody = "Error message or JSON here".toResponseBody(null) -// val errorResponse: Response = Response.error(400, errorResponseBody) -// viewModel.setPassword(validPassword) -// viewModel.setPasswordCheck(validPassword) -// coEvery { -// repository.resetPassword(AuthResetPasswordRequest(validPassword)) -// } returns flowOf(errorResponse) -// viewModel.resetPassword(onSuccess) -// Thread.sleep(10) -// -// assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD) -// assertEquals(viewModel.uiState.value.error?.messageId, R.string.unknown_error) -// } -// -// @Test -// fun `should fail when no internet connection`() { -// val onSuccess: () -> Unit = mockk(relaxed = true) -// val errorResponseBody = "Error message or JSON here".toResponseBody(null) -// val errorResponse: Response = Response.error(600, errorResponseBody) -// viewModel.setPassword(validPassword) -// viewModel.setPasswordCheck(validPassword) -// coEvery { -// repository.resetPassword(AuthResetPasswordRequest(validPassword)) -// } returns flowOf(errorResponse) -// viewModel.resetPassword(onSuccess) -// Thread.sleep(10) -// -// assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.CONNECTION) -// assertEquals(viewModel.uiState.value.error?.messageId, R.string.connection_error) -// } -//} \ No newline at end of file +package com.example.speechbuddy.viewmodel + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.example.speechbuddy.R +import com.example.speechbuddy.data.remote.requests.AuthResetPasswordRequest +import com.example.speechbuddy.domain.SessionManager +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.ui.models.ResetPasswordErrorType +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import retrofit2.Response + +class ResetPasswordViewModelTest { + @OptIn(DelicateCoroutinesApi::class) + private val mainThreadSurrogate = newSingleThreadContext("UI thread") + + @MockK + private val repository: AuthRepository = mockk() + private val sessionManager: SessionManager = mockk() + private lateinit var viewModel: ResetPasswordViewModel + + private val validPassword = "123456789" //length >= 8 + private val invalidPassword = "1234567" //length < 8 + private val emptyPassword = "" + private val passwordList = arrayListOf(validPassword, invalidPassword, emptyPassword) + private val invalidPasswordList = arrayListOf(invalidPassword, emptyPassword) + + @get:Rule + val instantExecutorRule = InstantTaskExecutorRule() + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setup() { + Dispatchers.setMain(mainThreadSurrogate) + viewModel = ResetPasswordViewModel(repository, sessionManager) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @After + fun tearDown() { + Dispatchers.resetMain() + mainThreadSurrogate.close() + } + + @Test + fun `should return no error when password is empty before click`() { + passwordList.forEach { + viewModel.setPassword(emptyPassword) + viewModel.setPasswordCheck(it) + assertEquals(viewModel.passwordInput, emptyPassword) + assertEquals(viewModel.passwordCheckInput, it) + assertEquals(viewModel.uiState.value.error?.type, null) + assertEquals(viewModel.uiState.value.isValidPassword, false) + } + } + + @Test + fun `should return no error when password is valid before click`() { + passwordList.forEach { + viewModel.setPassword(validPassword) + viewModel.setPasswordCheck(it) + assertEquals(viewModel.passwordInput, validPassword) + assertEquals(viewModel.passwordCheckInput, it) + assertEquals(viewModel.uiState.value.error?.type, null) + assertEquals(viewModel.uiState.value.isValidPassword, false) + } + } + + @Test + fun `should return no error when password is invalid before click`() { + passwordList.forEach { + viewModel.setPassword(invalidPassword) + viewModel.setPasswordCheck(it) + assertEquals(viewModel.passwordInput, invalidPassword) + assertEquals(viewModel.passwordCheckInput, it) + assertEquals(viewModel.uiState.value.error?.type, null) + assertEquals(viewModel.uiState.value.isValidPassword, false) + } + } + + @Test + fun `should set error type PASSWORD when password is empty`() { + val onSuccess: () -> Unit = mockk(relaxed = true) + passwordList.forEach { + viewModel.setPassword(emptyPassword) + viewModel.setPasswordCheck(it) + viewModel.resetPassword(onSuccess) + + assertEquals(viewModel.passwordInput, emptyPassword) + assertEquals(viewModel.passwordCheckInput, it) + assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD) + assertEquals(viewModel.uiState.value.error?.messageId, R.string.no_password) + assertEquals(viewModel.uiState.value.isValidPassword, false) + + viewModel.setPassword(validPassword) + assertEquals(viewModel.uiState.value.error?.type, null) + assertEquals(viewModel.uiState.value.isValidPassword, true) + } + } //covers both cases when password equals password check, or not + + @Test + fun `should set error type PASSWORD when password is invalid`() { + val onSuccess: () -> Unit = mockk(relaxed = true) + passwordList.forEach { + viewModel.setPassword(invalidPassword) + viewModel.setPasswordCheck(it) + viewModel.resetPassword(onSuccess) + + assertEquals(viewModel.passwordInput, invalidPassword) + assertEquals(viewModel.passwordCheckInput, it) + assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD) + assertEquals(viewModel.uiState.value.error?.messageId, R.string.password_too_short) + assertEquals(viewModel.uiState.value.isValidPassword, false) + + viewModel.setPassword(validPassword) + assertEquals(viewModel.uiState.value.error?.type, null) + assertEquals(viewModel.uiState.value.isValidPassword, true) + //checks whether validatePassword works well when changing the password into a valid one + } + } //covers both cases when password equals password check, or not + + @Test + fun `should set error type PASSWORD_CHECK when password is valid`() { + val onSuccess: () -> Unit = mockk(relaxed = true) + invalidPasswordList.forEach { + viewModel.setPassword(validPassword) + viewModel.setPasswordCheck(it) + viewModel.resetPassword(onSuccess) + + assertEquals(viewModel.passwordInput, validPassword) + assertEquals(viewModel.passwordCheckInput, it) + assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD_CHECK) + assertEquals(viewModel.uiState.value.error?.messageId, R.string.wrong_password_check) + assertEquals(viewModel.uiState.value.isValidPassword, false) + //isValidPassword doesn't turn true when password was valid from the start + } + } + + @Test + fun `should success when password is valid and equals password check`() { + val onSuccess: () -> Unit = mockk(relaxed = true) + viewModel.setPassword(validPassword) + viewModel.setPasswordCheck(validPassword) + coEvery { + repository.resetPassword(AuthResetPasswordRequest(validPassword)) + } returns flowOf(Response.success(null)) + viewModel.resetPassword(onSuccess) + Thread.sleep(10) + + assertEquals(viewModel.uiState.value.error?.type, null) + coVerify { sessionManager.deleteToken() } + } + + @Test + fun `should fail when bad request occurs`() { + val onSuccess: () -> Unit = mockk(relaxed = true) + val errorResponseBody = "Error message or JSON here".toResponseBody(null) + val errorResponse: Response = Response.error(400, errorResponseBody) + viewModel.setPassword(validPassword) + viewModel.setPasswordCheck(validPassword) + coEvery { + repository.resetPassword(AuthResetPasswordRequest(validPassword)) + } returns flowOf(errorResponse) + viewModel.resetPassword(onSuccess) + Thread.sleep(10) + + assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.PASSWORD) + assertEquals(viewModel.uiState.value.error?.messageId, R.string.unknown_error) + } + + @Test + fun `should fail when no internet connection`() { + val onSuccess: () -> Unit = mockk(relaxed = true) + val errorResponseBody = "Error message or JSON here".toResponseBody(null) + val errorResponse: Response = Response.error(600, errorResponseBody) + viewModel.setPassword(validPassword) + viewModel.setPasswordCheck(validPassword) + coEvery { + repository.resetPassword(AuthResetPasswordRequest(validPassword)) + } returns flowOf(errorResponse) + viewModel.resetPassword(onSuccess) + Thread.sleep(10) + + assertEquals(viewModel.uiState.value.error?.type, ResetPasswordErrorType.CONNECTION) + assertEquals(viewModel.uiState.value.error?.messageId, R.string.connection_error) + } +} \ No newline at end of file