From 41ca590801a9633999c442af1522dcc2c227e34d Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Wed, 28 Jan 2026 15:57:30 +0100 Subject: [PATCH 1/5] fix: do not refresh at startup --- .../playstore/PlayStoreAuthDataVerifier.kt | 25 ------- .../login/playstore/PlayStoreAuthenticator.kt | 14 +--- .../repository/AuthenticatorRepository.kt | 1 + .../e/apps/install/updates/UpdatesWorker.kt | 25 ++++--- .../PlayStoreAuthDataVerifierTest.kt | 67 ------------------- .../playstore/PlayStoreAuthenticatorTest.kt | 12 +--- 6 files changed, 20 insertions(+), 124 deletions(-) delete mode 100644 app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifier.kt delete mode 100644 app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifierTest.kt diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifier.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifier.kt deleted file mode 100644 index 2d837fe7b..000000000 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifier.kt +++ /dev/null @@ -1,25 +0,0 @@ -package foundation.e.apps.data.login.playstore - -import com.aurora.gplayapi.data.models.AuthData -import com.aurora.gplayapi.data.models.PlayResponse -import foundation.e.apps.data.playstore.utils.CustomAuthValidator -import foundation.e.apps.data.playstore.utils.GPlayHttpClient -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import javax.inject.Inject - -class PlayStoreAuthDataVerifier @Inject constructor( - private val gPlayHttpClient: GPlayHttpClient, -) { - suspend fun validate(authData: AuthData?): PlayResponse { - if (authData == null) { - return PlayResponse() - } - var result = PlayResponse() - withContext(Dispatchers.IO) { - val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) - result = authValidator.getValidityResponse() - } - return result - } -} diff --git a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt index 61244f540..4ae415f14 100644 --- a/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticator.kt @@ -40,7 +40,7 @@ import javax.inject.Singleton /** * Class to get GPlay auth data. Call [login] to get an already saved auth data - * or to fetch a new one for first use. Handles auth validation internally. + * or to fetch a new one for first use. * * https://gitlab.e.foundation/e/backlog/-/issues/5680 */ @@ -51,7 +51,6 @@ class PlayStoreAuthenticator @Inject constructor( private val appLoungeDataStore: AppLoungeDataStore, private val appLoungePreference: AppLoungePreference, private val authDataCache: AuthDataCache, - private val authDataVerifier: PlayStoreAuthDataVerifier, private val googleLoginManager: GoogleLoginManager, private val microgLoginManager: MicrogLoginManager, private val anonymousLoginManager: AnonymousLoginManager, @@ -89,7 +88,7 @@ class PlayStoreAuthenticator @Inject constructor( */ override suspend fun login(): StoreAuthResult { val user = appLoungeDataStore.getUser() - val validSavedAuth = getValidSavedAuthData() + val validSavedAuth = authDataCache.getSavedAuthData() val authDataResult = if (validSavedAuth == null) { retryWithBackoff { loginForUserType(user) } } else { @@ -204,15 +203,6 @@ class PlayStoreAuthenticator @Inject constructor( } } - private suspend fun isAuthDataValid(savedAuth: AuthData?): Boolean { - return savedAuth != null && authDataVerifier.validate(savedAuth).isSuccessful - } - - private suspend fun getValidSavedAuthData(): AuthData? { - val savedAuth = authDataCache.getSavedAuthData() - return if (isAuthDataValid(savedAuth)) savedAuth else null - } - private fun buildErrorAuthResult( user: User, authDataResult: ResultSupreme diff --git a/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt b/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt index fb2e4990a..a514d9651 100644 --- a/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/login/repository/AuthenticatorRepository.kt @@ -69,6 +69,7 @@ class AuthenticatorRepository @Inject constructor( suspend fun getValidatedAuthData(): ResultSupreme { val authenticator = authenticators.firstOrNull { it.storeType == StoreType.PLAY_STORE } ?: return ResultSupreme.Error("Play Store authenticator not available") + authenticator.logout() val authResult = authenticator.login() authResult.authDataToPersist?.let { appLoungeDataStore.saveAuthData(it) } val authObject = authResult.authObject as? AuthObject.GPlayAuth diff --git a/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt b/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt index 48a7c9155..032344b87 100644 --- a/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt +++ b/app/src/main/java/foundation/e/apps/install/updates/UpdatesWorker.kt @@ -115,16 +115,16 @@ class UpdatesWorker @AssistedInject constructor( val appsNeededToUpdate = mutableListOf() val user = getUser() val loginState = getLoginState() - val authData = authenticatorRepository.getValidatedAuthData().data + val authData = appLoungeDataStore.getAuthData() val resultStatus: ResultStatus - if (user in listOf(User.ANONYMOUS, User.GOOGLE) && authData != null) { + if (user in listOf(User.ANONYMOUS, User.GOOGLE)) { /* * Signifies valid Google user and valid auth data to update * apps from Google Play store. * The user check will be more useful in No-Google mode. */ - val updateData = updatesManagerRepository.getUpdates() + val updateData = fetchUpdatesWithAuthRetry() appsNeededToUpdate.addAll(updateData.first) resultStatus = updateData.second } else if (loginState != LoginState.UNAVAILABLE) { @@ -161,16 +161,23 @@ class UpdatesWorker @AssistedInject constructor( triggerUpdateProcessOnSettings( isConnectedToUnMeteredNetwork, appsNeededToUpdate, - /* - * If authData is null, only cleanApk data will be present - * in appsNeededToUpdate list. Hence it is safe to proceed with - * blank AuthData. - */ - authData ?: AuthData("", ""), + authData, ) } } + private suspend fun fetchUpdatesWithAuthRetry(): Pair, ResultStatus> { + val updateData = updatesManagerRepository.getUpdates() + if (updateData.second == ResultStatus.OK) { + return updateData + } + + val refreshedAuth = authenticatorRepository.getValidatedAuthData().data + val shouldRetry = refreshedAuth != null + + return if (shouldRetry) updatesManagerRepository.getUpdates() else updateData + } + private suspend fun manageRetry(extraMessage: String) { retryCount++ if (retryCount == 1) { diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifierTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifierTest.kt deleted file mode 100644 index 84a3c2649..000000000 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthDataVerifierTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -package foundation.e.apps.data.login.playstore - -import com.aurora.gplayapi.data.models.AuthData -import com.aurora.gplayapi.data.models.PlayResponse -import com.aurora.gplayapi.data.providers.DeviceInfoProvider -import com.google.common.truth.Truth.assertThat -import foundation.e.apps.data.playstore.utils.GPlayHttpClient -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.test.runTest -import org.junit.Test -import java.util.Locale - -class PlayStoreAuthDataVerifierTest { - - private val gPlayHttpClient: GPlayHttpClient = mockk() - private val verifier = PlayStoreAuthDataVerifier(gPlayHttpClient) - - @Test - fun validate_returnsEmptyResponseWhenAuthDataIsNull() = runTest { - val result = verifier.validate(null) - - assertThat(result).isNotNull() - verify(exactly = 0) { - gPlayHttpClient.post( - any(), - any>(), - any>() - ) - } - } - - @Test - fun validate_usesHttpClientWhenAuthDataIsPresent() = runTest { - val deviceInfoProvider: DeviceInfoProvider = mockk { - every { userAgentString } returns "ua" - every { mccMnc } returns "" - } - val authData = AuthData( - email = "email", - authToken = "token", - gsfId = "gsf", - locale = Locale.US, - deviceInfoProvider = deviceInfoProvider - ) - val response = PlayResponse() - every { - gPlayHttpClient.post( - any(), - any>(), - any>() - ) - } returns response - - val result = verifier.validate(authData) - - assertThat(result).isEqualTo(response) - verify(exactly = 1) { - gPlayHttpClient.post( - any(), - any>(), - any>() - ) - } - } -} diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt index 298e82221..8b9c85966 100644 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/playstore/PlayStoreAuthenticatorTest.kt @@ -2,7 +2,6 @@ package foundation.e.apps.data.login.playstore import android.content.Context import com.aurora.gplayapi.data.models.AuthData -import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.data.models.UserProfile import com.google.common.truth.Truth.assertThat import foundation.e.apps.data.ResultSupreme @@ -21,12 +20,11 @@ import org.junit.Test class PlayStoreAuthenticatorTest { @Test - fun login_returnsSavedAuthWhenValid() = runTest { + fun login_returnsSavedAuthWhenAvailable() = runTest { val context = mockk(relaxed = true) val appLoungeDataStore = mockk() val appLoungePreference = mockk(relaxed = true) val authDataCache = mockk() - val authDataVerifier = mockk() val googleLoginManager = mockk(relaxed = true) val microgLoginManager = mockk(relaxed = true) val anonymousLoginManager = mockk(relaxed = true) @@ -34,11 +32,8 @@ class PlayStoreAuthenticatorTest { val userProfileFetcher = mockk(relaxed = true) val savedAuth = AuthData(email = "saved@example.com") - val playResponse = mockk() - every { playResponse.isSuccessful } returns true every { appLoungeDataStore.getUser() } returns User.ANONYMOUS every { authDataCache.getSavedAuthData() } returns savedAuth - coEvery { authDataVerifier.validate(savedAuth) } returns playResponse every { authDataCache.formatAuthData(savedAuth) } returns savedAuth val authenticator = PlayStoreAuthenticator( @@ -46,7 +41,6 @@ class PlayStoreAuthenticatorTest { appLoungeDataStore = appLoungeDataStore, appLoungePreference = appLoungePreference, authDataCache = authDataCache, - authDataVerifier = authDataVerifier, googleLoginManager = googleLoginManager, microgLoginManager = microgLoginManager, anonymousLoginManager = anonymousLoginManager, @@ -69,7 +63,6 @@ class PlayStoreAuthenticatorTest { val appLoungeDataStore = mockk() val appLoungePreference = mockk(relaxed = true) val authDataCache = mockk() - val authDataVerifier = mockk() val googleLoginManager = mockk(relaxed = true) val microgLoginManager = mockk(relaxed = true) val anonymousLoginManager = mockk() @@ -87,7 +80,6 @@ class PlayStoreAuthenticatorTest { appLoungeDataStore = appLoungeDataStore, appLoungePreference = appLoungePreference, authDataCache = authDataCache, - authDataVerifier = authDataVerifier, googleLoginManager = googleLoginManager, microgLoginManager = microgLoginManager, anonymousLoginManager = anonymousLoginManager, @@ -110,7 +102,6 @@ class PlayStoreAuthenticatorTest { val appLoungeDataStore = mockk() val appLoungePreference = mockk(relaxed = true) val authDataCache = mockk() - val authDataVerifier = mockk() val googleLoginManager = mockk() val microgLoginManager = mockk(relaxed = true) val anonymousLoginManager = mockk(relaxed = true) @@ -132,7 +123,6 @@ class PlayStoreAuthenticatorTest { appLoungeDataStore = appLoungeDataStore, appLoungePreference = appLoungePreference, authDataCache = authDataCache, - authDataVerifier = authDataVerifier, googleLoginManager = googleLoginManager, microgLoginManager = microgLoginManager, anonymousLoginManager = anonymousLoginManager, -- GitLab From 5d4743989d34756b0da999cba4e5d509bbcb3363 Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Thu, 29 Jan 2026 14:03:30 +0100 Subject: [PATCH 2/5] refactor: rename CompleteMicrogLoginUseCase to InitialMicrogLoginUseCase to be consistent with others inital*UseCases --- ...ginUseCase.kt => InitialMicrogLoginUseCase.kt} | 2 +- .../java/foundation/e/apps/ui/LoginViewModel.kt | 6 +++--- ...seTest.kt => InitialMicrogLoginUseCaseTest.kt} | 4 ++-- .../foundation/e/apps/login/LoginViewModelTest.kt | 15 +++++++-------- 4 files changed, 13 insertions(+), 14 deletions(-) rename app/src/main/java/foundation/e/apps/domain/login/{CompleteMicrogLoginUseCase.kt => InitialMicrogLoginUseCase.kt} (94%) rename app/src/test/java/foundation/e/apps/domain/login/{CompleteMicrogLoginUseCaseTest.kt => InitialMicrogLoginUseCaseTest.kt} (92%) diff --git a/app/src/main/java/foundation/e/apps/domain/login/CompleteMicrogLoginUseCase.kt b/app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt similarity index 94% rename from app/src/main/java/foundation/e/apps/domain/login/CompleteMicrogLoginUseCase.kt rename to app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt index b739edf81..a8a608aad 100644 --- a/app/src/main/java/foundation/e/apps/domain/login/CompleteMicrogLoginUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCase.kt @@ -9,7 +9,7 @@ import foundation.e.apps.data.login.repository.AuthenticatorRepository import foundation.e.apps.data.login.state.LoginCoordinator import javax.inject.Inject -class CompleteMicrogLoginUseCase @Inject constructor( +class InitialMicrogLoginUseCase @Inject constructor( private val stores: Stores, private val loginCoordinator: LoginCoordinator, private val authenticatorRepository: AuthenticatorRepository diff --git a/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt b/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt index 3f55039b7..6cde0b0e3 100644 --- a/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt @@ -28,11 +28,11 @@ import foundation.e.apps.R import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.login.microg.MicrogLoginManager -import foundation.e.apps.domain.login.CompleteMicrogLoginUseCase import foundation.e.apps.domain.login.FetchMicrogAccountUseCase import foundation.e.apps.domain.login.HasMicrogAccountUseCase import foundation.e.apps.domain.login.InitialAnonymousLoginUseCase import foundation.e.apps.domain.login.InitialGoogleLoginUseCase +import foundation.e.apps.domain.login.InitialMicrogLoginUseCase import foundation.e.apps.domain.login.InitialNoGoogleLoginUseCase import foundation.e.apps.domain.login.LogoutUseCase import foundation.e.apps.domain.login.StartLoginFlowUseCase @@ -51,7 +51,7 @@ class LoginViewModel @Inject constructor( private val startLoginFlowUseCase: StartLoginFlowUseCase, private val hasMicrogAccountUseCase: HasMicrogAccountUseCase, private val fetchMicrogAccountUseCase: FetchMicrogAccountUseCase, - private val completeMicrogLoginUseCase: CompleteMicrogLoginUseCase, + private val initialMicrogLoginUseCase: InitialMicrogLoginUseCase, private val initialAnonymousLoginUseCase: InitialAnonymousLoginUseCase, private val initialGoogleLoginUseCase: InitialGoogleLoginUseCase, private val initialNoGoogleLoginUseCase: InitialNoGoogleLoginUseCase, @@ -98,7 +98,7 @@ class LoginViewModel @Inject constructor( }.onSuccess { result -> when (result) { is MicrogLoginManager.FetchResult.Success -> { - completeMicrogLoginUseCase(result.microgAccount) + initialMicrogLoginUseCase(result.microgAccount) onUserSaved() startLoginFlow() } diff --git a/app/src/test/java/foundation/e/apps/domain/login/CompleteMicrogLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt similarity index 92% rename from app/src/test/java/foundation/e/apps/domain/login/CompleteMicrogLoginUseCaseTest.kt rename to app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt index 6882429fb..d6245902e 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/CompleteMicrogLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt @@ -18,12 +18,12 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [30]) -class CompleteMicrogLoginUseCaseTest { +class InitialMicrogLoginUseCaseTest { private val stores: Stores = mockk(relaxed = true) private val loginCoordinator: LoginCoordinator = mockk(relaxed = true) private val authenticatorRepository: AuthenticatorRepository = mockk(relaxed = true) - private val useCase = CompleteMicrogLoginUseCase(stores, loginCoordinator, authenticatorRepository) + private val useCase = InitialMicrogLoginUseCase(stores, loginCoordinator, authenticatorRepository) @Test fun invoke_updatesStoreAndCredentials() = runTest { diff --git a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt index 03513e682..cc25b42ad 100644 --- a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt +++ b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt @@ -29,11 +29,11 @@ import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.microg.MicrogAccount import foundation.e.apps.data.login.microg.MicrogLoginManager -import foundation.e.apps.domain.login.CompleteMicrogLoginUseCase import foundation.e.apps.domain.login.FetchMicrogAccountUseCase import foundation.e.apps.domain.login.HasMicrogAccountUseCase import foundation.e.apps.domain.login.InitialAnonymousLoginUseCase import foundation.e.apps.domain.login.InitialGoogleLoginUseCase +import foundation.e.apps.domain.login.InitialMicrogLoginUseCase import foundation.e.apps.domain.login.InitialNoGoogleLoginUseCase import foundation.e.apps.domain.login.LogoutUseCase import foundation.e.apps.domain.login.StartLoginFlowUseCase @@ -66,7 +66,7 @@ class LoginViewModelTest { @Mock private lateinit var fetchMicrogAccountUseCase: FetchMicrogAccountUseCase @Mock - private lateinit var completeMicrogLoginUseCase: CompleteMicrogLoginUseCase + private lateinit var initialMicrogLoginUseCase: InitialMicrogLoginUseCase @Mock private lateinit var initialAnonymousLoginUseCase: InitialAnonymousLoginUseCase @Mock @@ -94,7 +94,7 @@ class LoginViewModelTest { startLoginFlowUseCase, hasMicrogAccountUseCase, fetchMicrogAccountUseCase, - completeMicrogLoginUseCase, + initialMicrogLoginUseCase, initialAnonymousLoginUseCase, initialGoogleLoginUseCase, initialNoGoogleLoginUseCase, @@ -135,8 +135,7 @@ class LoginViewModelTest { val microgAccount = MicrogAccount(account, "token") val result = MicrogLoginManager.FetchResult.Success(microgAccount) - whenever(fetchMicrogAccountUseCase.invoke("user@gmail.com")).thenReturn(result) - whenever(completeMicrogLoginUseCase.invoke(microgAccount)).thenReturn(Unit) + whenever(initialMicrogLoginUseCase.invoke(microgAccount)).thenReturn(Unit) whenever(startLoginFlowUseCase.invoke(any())).thenReturn(emptyList()) var onErrorCalls = 0 @@ -149,7 +148,7 @@ class LoginViewModelTest { onIntentRequired = { onIntentCalls += 1 } ) - verify(completeMicrogLoginUseCase).invoke(microgAccount) + verify(initialMicrogLoginUseCase).invoke(microgAccount) verify(startLoginFlowUseCase).invoke(any()) assert(onErrorCalls == 0) assert(onIntentCalls == 0) @@ -177,7 +176,7 @@ class LoginViewModelTest { assert(onIntentCalls == 1) assert(receivedIntent === intent) - verify(completeMicrogLoginUseCase, never()).invoke(any()) + verify(initialMicrogLoginUseCase, never()).invoke(any()) } @Test @@ -195,6 +194,6 @@ class LoginViewModelTest { ) assert(errorMessage == "boom") - verify(completeMicrogLoginUseCase, never()).invoke(any()) + verify(initialMicrogLoginUseCase, never()).invoke(any()) } } -- GitLab From 28ec2197d8de454f4fd844f3889ecf7e737a6ebf Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Thu, 29 Jan 2026 14:33:14 +0100 Subject: [PATCH 3/5] refactor: remove StartLoginFlowUseCase Integrate the logic within other Init*LoginUseCase. --- .../domain/login/StartLoginFlowUseCase.kt | 14 -------- .../foundation/e/apps/ui/LoginViewModel.kt | 6 ++-- .../login/InitialAnonymousLoginUseCaseTest.kt | 3 +- .../login/InitialGoogleLoginUseCaseTest.kt | 3 +- .../login/InitialMicrogLoginUseCaseTest.kt | 1 - .../login/InitialNoGoogleLoginUseCaseTest.kt | 3 +- .../domain/login/StartLoginFlowUseCaseTest.kt | 34 ------------------- .../e/apps/login/LoginViewModelTest.kt | 11 +++--- 8 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 app/src/main/java/foundation/e/apps/domain/login/StartLoginFlowUseCase.kt delete mode 100644 app/src/test/java/foundation/e/apps/domain/login/StartLoginFlowUseCaseTest.kt diff --git a/app/src/main/java/foundation/e/apps/domain/login/StartLoginFlowUseCase.kt b/app/src/main/java/foundation/e/apps/domain/login/StartLoginFlowUseCase.kt deleted file mode 100644 index a68050cea..000000000 --- a/app/src/main/java/foundation/e/apps/domain/login/StartLoginFlowUseCase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package foundation.e.apps.domain.login - -import foundation.e.apps.data.login.core.AuthObject -import foundation.e.apps.data.login.core.StoreType -import foundation.e.apps.data.login.repository.AuthenticatorRepository -import javax.inject.Inject - -class StartLoginFlowUseCase @Inject constructor( - private val authenticatorRepository: AuthenticatorRepository -) { - suspend operator fun invoke(clearList: List): List { - return authenticatorRepository.fetchAuthObjects(clearList) - } -} diff --git a/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt b/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt index 6cde0b0e3..8ed768eb5 100644 --- a/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/LoginViewModel.kt @@ -28,6 +28,7 @@ import foundation.e.apps.R import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreType import foundation.e.apps.data.login.microg.MicrogLoginManager +import foundation.e.apps.data.login.repository.AuthenticatorRepository import foundation.e.apps.domain.login.FetchMicrogAccountUseCase import foundation.e.apps.domain.login.HasMicrogAccountUseCase import foundation.e.apps.domain.login.InitialAnonymousLoginUseCase @@ -35,7 +36,6 @@ import foundation.e.apps.domain.login.InitialGoogleLoginUseCase import foundation.e.apps.domain.login.InitialMicrogLoginUseCase import foundation.e.apps.domain.login.InitialNoGoogleLoginUseCase import foundation.e.apps.domain.login.LogoutUseCase -import foundation.e.apps.domain.login.StartLoginFlowUseCase import foundation.e.apps.ui.parentFragment.LoadingViewModel import kotlinx.coroutines.launch import okhttp3.Cache @@ -48,7 +48,7 @@ import javax.inject.Inject @HiltViewModel @Suppress("LongParameterList") class LoginViewModel @Inject constructor( - private val startLoginFlowUseCase: StartLoginFlowUseCase, + private val authenticatorRepository: AuthenticatorRepository, private val hasMicrogAccountUseCase: HasMicrogAccountUseCase, private val fetchMicrogAccountUseCase: FetchMicrogAccountUseCase, private val initialMicrogLoginUseCase: InitialMicrogLoginUseCase, @@ -78,7 +78,7 @@ class LoginViewModel @Inject constructor( */ fun startLoginFlow(clearList: List = listOf()) { viewModelScope.launch { - val authObjectsLocal = startLoginFlowUseCase(clearList) + val authObjectsLocal = authenticatorRepository.fetchAuthObjects(clearList) authObjects.postValue(authObjectsLocal) } } diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt index f0f051ae0..bc0ad1c56 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialAnonymousLoginUseCaseTest.kt @@ -4,9 +4,9 @@ import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.state.LoginCoordinator -import io.mockk.every import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -21,7 +21,6 @@ class InitialAnonymousLoginUseCaseTest { fun invoke_setsAnonymousUser() = runTest { every { stores.enableStore(Source.PLAY_STORE) } returns Unit coEvery { loginCoordinator.saveUserType(User.ANONYMOUS) } returns Unit - useCase() io.mockk.verify { stores.enableStore(Source.PLAY_STORE) } diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt index ac3625321..221272f1d 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialGoogleLoginUseCaseTest.kt @@ -5,9 +5,9 @@ import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.playstore.PlayStoreAuthSource import foundation.e.apps.data.login.state.LoginCoordinator -import io.mockk.every import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -24,7 +24,6 @@ class InitialGoogleLoginUseCaseTest { coEvery { loginCoordinator.saveGoogleLogin("email", "oauth") } returns Unit coEvery { loginCoordinator.savePlayStoreAuthSource(PlayStoreAuthSource.GOOGLE) } returns Unit coEvery { loginCoordinator.saveUserType(User.GOOGLE) } returns Unit - useCase("email", "oauth") io.mockk.verify { stores.enableStore(Source.PLAY_STORE) } diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt index d6245902e..f437f810b 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialMicrogLoginUseCaseTest.kt @@ -30,7 +30,6 @@ class InitialMicrogLoginUseCaseTest { val accountName = "user@email" val account = android.accounts.Account(accountName, "type") val microgAccount = MicrogAccount(account, "token") - useCase(microgAccount) coVerify { stores.enableStore(Source.PLAY_STORE) } diff --git a/app/src/test/java/foundation/e/apps/domain/login/InitialNoGoogleLoginUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/InitialNoGoogleLoginUseCaseTest.kt index cc1939538..b1fb892bb 100644 --- a/app/src/test/java/foundation/e/apps/domain/login/InitialNoGoogleLoginUseCaseTest.kt +++ b/app/src/test/java/foundation/e/apps/domain/login/InitialNoGoogleLoginUseCaseTest.kt @@ -3,9 +3,9 @@ package foundation.e.apps.domain.login import foundation.e.apps.data.Stores import foundation.e.apps.data.enums.Source import foundation.e.apps.data.login.state.LoginCoordinator -import io.mockk.every import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -20,7 +20,6 @@ class InitialNoGoogleLoginUseCaseTest { fun invoke_setsNoGoogleMode() = runTest { every { stores.disableStore(Source.PLAY_STORE) } returns Unit coEvery { loginCoordinator.setNoGoogleMode() } returns Unit - useCase() io.mockk.verify { stores.disableStore(Source.PLAY_STORE) } diff --git a/app/src/test/java/foundation/e/apps/domain/login/StartLoginFlowUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/StartLoginFlowUseCaseTest.kt deleted file mode 100644 index f88cf8d56..000000000 --- a/app/src/test/java/foundation/e/apps/domain/login/StartLoginFlowUseCaseTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package foundation.e.apps.domain.login - -import com.google.common.truth.Truth.assertThat -import com.aurora.gplayapi.data.models.AuthData -import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.login.core.AuthObject -import foundation.e.apps.data.login.core.StoreType -import foundation.e.apps.data.login.repository.AuthenticatorRepository -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import kotlinx.coroutines.test.runTest -import org.junit.Test - -class StartLoginFlowUseCaseTest { - - private val authenticatorRepository: AuthenticatorRepository = mockk(relaxed = true) - private val useCase = StartLoginFlowUseCase(authenticatorRepository) - - @Test - fun invoke_returnsAuthObjects() = runTest { - val authObjects = listOf( - AuthObject.GPlayAuth(ResultSupreme.Success(AuthData("email")), User.GOOGLE) - ) - val clearList = listOf(StoreType.PLAY_STORE) - coEvery { authenticatorRepository.fetchAuthObjects(clearList) } returns authObjects - - val result = useCase(clearList) - - assertThat(result).isEqualTo(authObjects) - coVerify(exactly = 1) { authenticatorRepository.fetchAuthObjects(clearList) } - } -} diff --git a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt index cc25b42ad..65e4dbd7f 100644 --- a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt +++ b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt @@ -29,6 +29,7 @@ import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.microg.MicrogAccount import foundation.e.apps.data.login.microg.MicrogLoginManager +import foundation.e.apps.data.login.repository.AuthenticatorRepository import foundation.e.apps.domain.login.FetchMicrogAccountUseCase import foundation.e.apps.domain.login.HasMicrogAccountUseCase import foundation.e.apps.domain.login.InitialAnonymousLoginUseCase @@ -36,7 +37,6 @@ import foundation.e.apps.domain.login.InitialGoogleLoginUseCase import foundation.e.apps.domain.login.InitialMicrogLoginUseCase import foundation.e.apps.domain.login.InitialNoGoogleLoginUseCase import foundation.e.apps.domain.login.LogoutUseCase -import foundation.e.apps.domain.login.StartLoginFlowUseCase import foundation.e.apps.ui.LoginViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,7 +60,7 @@ import org.mockito.kotlin.whenever class LoginViewModelTest { @Mock - private lateinit var startLoginFlowUseCase: StartLoginFlowUseCase + private lateinit var authenticatorRepository: AuthenticatorRepository @Mock private lateinit var hasMicrogAccountUseCase: HasMicrogAccountUseCase @Mock @@ -91,7 +91,7 @@ class LoginViewModelTest { MockitoAnnotations.openMocks(this) Dispatchers.setMain(UnconfinedTestDispatcher()) loginViewModel = LoginViewModel( - startLoginFlowUseCase, + authenticatorRepository, hasMicrogAccountUseCase, fetchMicrogAccountUseCase, initialMicrogLoginUseCase, @@ -135,8 +135,9 @@ class LoginViewModelTest { val microgAccount = MicrogAccount(account, "token") val result = MicrogLoginManager.FetchResult.Success(microgAccount) + whenever(fetchMicrogAccountUseCase.invoke("user@gmail.com")).thenReturn(result) whenever(initialMicrogLoginUseCase.invoke(microgAccount)).thenReturn(Unit) - whenever(startLoginFlowUseCase.invoke(any())).thenReturn(emptyList()) + whenever(authenticatorRepository.fetchAuthObjects(any())).thenReturn(emptyList()) var onErrorCalls = 0 var onIntentCalls = 0 @@ -149,7 +150,7 @@ class LoginViewModelTest { ) verify(initialMicrogLoginUseCase).invoke(microgAccount) - verify(startLoginFlowUseCase).invoke(any()) + verify(authenticatorRepository).fetchAuthObjects(any()) assert(onErrorCalls == 0) assert(onIntentCalls == 0) } -- GitLab From 1581a892bcfec42b4465eddb7f3b6167492f20da Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Thu, 29 Jan 2026 18:09:05 +0100 Subject: [PATCH 4/5] refactor: remove useless UseCase classes invoke() calls --- .../install/workmanager/AppInstallProcessor.kt | 2 +- .../e/apps/provider/AgeRatingProvider.kt | 4 ++-- .../installProcessor/AppInstallProcessorTest.kt | 4 ++-- .../foundation/e/apps/login/LoginViewModelTest.kt | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index 9f87309d1..d7e933e05 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -173,7 +173,7 @@ class AppInstallProcessor @Inject constructor( } private suspend fun validateAgeLimit(appInstall: AppInstall): Boolean { - val ageLimitValidationResult = validateAppAgeLimitUseCase.invoke(appInstall) + val ageLimitValidationResult = validateAppAgeLimitUseCase(appInstall) if (ageLimitValidationResult.data?.isValid == true) { return true } diff --git a/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt b/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt index c936c885b..80fd58e22 100644 --- a/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt +++ b/app/src/main/java/foundation/e/apps/provider/AgeRatingProvider.kt @@ -223,7 +223,7 @@ class AgeRatingProvider : ContentProvider() { packageName = packageName, source = Source.PLAY_STORE ) - val validateResult = validateAppAgeLimitUseCase.invoke(fakeAppInstall) + val validateResult = validateAppAgeLimitUseCase(fakeAppInstall) saveContentRatingIfInvalid(validateResult, packageName) return validateResult.data?.isValid @@ -249,7 +249,7 @@ class AgeRatingProvider : ContentProvider() { packageName = packageName, source = Source.OPEN_SOURCE, ) - val validateResult = validateAppAgeLimitUseCase.invoke(fakeAppInstall) + val validateResult = validateAppAgeLimitUseCase(fakeAppInstall) return validateResult.data?.isValid ?: false } diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt index a11efa092..98c23a525 100644 --- a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -190,7 +190,7 @@ class AppInstallProcessorTest { @Test fun `processInstallTest when age limit is satisfied`() = runTest { val fusedDownload = initTest() - Mockito.`when`(validateAppAgeRatingUseCase.invoke(fusedDownload)) + Mockito.`when`(validateAppAgeRatingUseCase(fusedDownload)) .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true))) val finalFusedDownload = runProcessInstall(fusedDownload) @@ -200,7 +200,7 @@ class AppInstallProcessorTest { @Test fun `processInstallTest when age limit is not satisfied`() = runTest { val fusedDownload = initTest() - Mockito.`when`(validateAppAgeRatingUseCase.invoke(fusedDownload)) + Mockito.`when`(validateAppAgeRatingUseCase(fusedDownload)) .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(false))) val finalFusedDownload = runProcessInstall(fusedDownload) diff --git a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt index 65e4dbd7f..e9dc6c757 100644 --- a/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt +++ b/app/src/test/java/foundation/e/apps/login/LoginViewModelTest.kt @@ -135,8 +135,8 @@ class LoginViewModelTest { val microgAccount = MicrogAccount(account, "token") val result = MicrogLoginManager.FetchResult.Success(microgAccount) - whenever(fetchMicrogAccountUseCase.invoke("user@gmail.com")).thenReturn(result) - whenever(initialMicrogLoginUseCase.invoke(microgAccount)).thenReturn(Unit) + whenever(fetchMicrogAccountUseCase("user@gmail.com")).thenReturn(result) + whenever(initialMicrogLoginUseCase(microgAccount)).thenReturn(Unit) whenever(authenticatorRepository.fetchAuthObjects(any())).thenReturn(emptyList()) var onErrorCalls = 0 @@ -149,7 +149,7 @@ class LoginViewModelTest { onIntentRequired = { onIntentCalls += 1 } ) - verify(initialMicrogLoginUseCase).invoke(microgAccount) + verify(initialMicrogLoginUseCase)(microgAccount) verify(authenticatorRepository).fetchAuthObjects(any()) assert(onErrorCalls == 0) assert(onIntentCalls == 0) @@ -160,7 +160,7 @@ class LoginViewModelTest { val intent = Intent("test.action") val result = MicrogLoginManager.FetchResult.RequiresUserAction(intent) - whenever(fetchMicrogAccountUseCase.invoke("")).thenReturn(result) + whenever(fetchMicrogAccountUseCase("")).thenReturn(result) var onIntentCalls = 0 var receivedIntent: Intent? = null @@ -177,13 +177,13 @@ class LoginViewModelTest { assert(onIntentCalls == 1) assert(receivedIntent === intent) - verify(initialMicrogLoginUseCase, never()).invoke(any()) + verify(initialMicrogLoginUseCase, never())(any()) } @Test fun `initialMicrogLogin reports error when fetch fails`() = runTest { val failure = IllegalStateException("boom") - whenever(fetchMicrogAccountUseCase.invoke("")).thenThrow(failure) + whenever(fetchMicrogAccountUseCase("")).thenThrow(failure) var errorMessage: String? = null @@ -195,6 +195,6 @@ class LoginViewModelTest { ) assert(errorMessage == "boom") - verify(initialMicrogLoginUseCase, never()).invoke(any()) + verify(initialMicrogLoginUseCase, never())(any()) } } -- GitLab From 3edcb7c753ec034b971a60084f76c6381f47a05c Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Fri, 30 Jan 2026 14:48:09 +0100 Subject: [PATCH 5/5] tests: add more unit tests --- .../repository/AuthenticatorRepositoryTest.kt | 121 ++++++++++++++ .../apps/install/updates/UpdatesWorkerTest.kt | 149 ++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt create mode 100644 app/src/test/java/foundation/e/apps/install/updates/UpdatesWorkerTest.kt diff --git a/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt b/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt new file mode 100644 index 000000000..5498988c7 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/data/login/repository/AuthenticatorRepositoryTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2026 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.data.login.repository + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.aurora.gplayapi.data.models.AuthData +import com.google.common.truth.Truth.assertThat +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.enums.User +import foundation.e.apps.data.login.core.AuthObject +import foundation.e.apps.data.login.core.StoreAuthResult +import foundation.e.apps.data.login.core.StoreAuthenticator +import foundation.e.apps.data.login.core.StoreType +import foundation.e.apps.data.preference.AppLoungeDataStore +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [30]) +class AuthenticatorRepositoryTest { + + @Test + fun getGPlayAuthOrThrow_returnsStoredAuth() = runTest { + val authData = AuthData(email = "user@example.com") + val appLoungeDataStore = createDataStore() + appLoungeDataStore.saveAuthData(authData) + val repository = AuthenticatorRepository(emptyList(), appLoungeDataStore) + + val result = repository.getGPlayAuthOrThrow() + + assertThat(result).isEqualTo(authData) + } + + @Test + fun fetchAuthObjects_logsOutAndPersistsAuthData() = runTest { + val authData = AuthData(email = "user@example.com") + val authObject = AuthObject.GPlayAuth(ResultSupreme.Success(authData), User.GOOGLE) + val storeResult = StoreAuthResult(authObject, authData) + val appLoungeDataStore = createDataStore() + val playAuthenticator = mockk() + val inactiveAuthenticator = mockk() + + every { playAuthenticator.storeType } returns StoreType.PLAY_STORE + every { inactiveAuthenticator.storeType } returns StoreType.CLEAN_APK + every { playAuthenticator.isStoreActive() } returns true + every { inactiveAuthenticator.isStoreActive() } returns false + coEvery { playAuthenticator.logout() } returns Unit + coEvery { playAuthenticator.login() } returns storeResult + val repository = AuthenticatorRepository(listOf(playAuthenticator, inactiveAuthenticator), appLoungeDataStore) + + val result = repository.fetchAuthObjects(listOf(StoreType.PLAY_STORE)) + + assertThat(result).containsExactly(authObject) + coVerify { playAuthenticator.logout() } + assertThat(appLoungeDataStore.getAuthData()).isEqualTo(authData) + } + + @Test + fun getValidatedAuthData_returnsErrorWhenMissingPlayStoreAuthenticator() = runTest { + val appLoungeDataStore = createDataStore() + val cleanApkAuthenticator = mockk() + every { cleanApkAuthenticator.storeType } returns StoreType.CLEAN_APK + val repository = AuthenticatorRepository(listOf(cleanApkAuthenticator), appLoungeDataStore) + + val result = repository.getValidatedAuthData() + + assertThat(result).isInstanceOf(ResultSupreme.Error::class.java) + } + + @Test + fun getValidatedAuthData_logsOutAndPersistsAuthData() = runTest { + val authData = AuthData(email = "user@example.com") + val authObject = AuthObject.GPlayAuth(ResultSupreme.Success(authData), User.GOOGLE) + val storeResult = StoreAuthResult(authObject, authData) + val appLoungeDataStore = createDataStore() + val playAuthenticator = mockk() + + every { playAuthenticator.storeType } returns StoreType.PLAY_STORE + coEvery { playAuthenticator.logout() } returns Unit + coEvery { playAuthenticator.login() } returns storeResult + + val repository = AuthenticatorRepository(listOf(playAuthenticator), appLoungeDataStore) + + val result = repository.getValidatedAuthData() + + assertThat(result).isInstanceOf(ResultSupreme.Success::class.java) + coVerify { playAuthenticator.logout() } + assertThat(appLoungeDataStore.getAuthData()).isEqualTo(authData) + } + + private fun createDataStore(): AppLoungeDataStore { + val context = ApplicationProvider.getApplicationContext() + val json = Json { ignoreUnknownKeys = true } + return AppLoungeDataStore(context, json) + } +} diff --git a/app/src/test/java/foundation/e/apps/install/updates/UpdatesWorkerTest.kt b/app/src/test/java/foundation/e/apps/install/updates/UpdatesWorkerTest.kt new file mode 100644 index 000000000..f069f13d1 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/install/updates/UpdatesWorkerTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2026 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.install.updates + +import android.app.NotificationManager +import android.content.Context +import android.content.SharedPreferences +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.os.Build +import androidx.work.Data +import androidx.work.WorkerParameters +import com.aurora.gplayapi.data.models.AuthData +import com.google.common.truth.Truth.assertThat +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.blockedApps.BlockedAppRepository +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.User +import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository +import foundation.e.apps.data.login.core.AuthObject +import foundation.e.apps.data.login.core.StoreAuthResult +import foundation.e.apps.data.login.core.StoreAuthenticator +import foundation.e.apps.data.login.core.StoreType +import foundation.e.apps.data.login.repository.AuthenticatorRepository +import foundation.e.apps.data.login.state.LoginState +import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.updates.UpdatesManagerRepository +import foundation.e.apps.install.workmanager.AppInstallProcessor +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.N]) +class UpdatesWorkerTest { + + @Test + fun doWork_retriesUpdatesAfterAuthRefresh() = runTest { + val dataStoreContext = RuntimeEnvironment.getApplication() + val workerContext = mock() + val sharedPreferences = mock() + val connectivityManager = mock() + val network = mock() + val networkCapabilities = mock() + val notificationManager = mock() + val params = mock() + val updatesManagerRepository = mock() + val appLoungeDataStore = createDataStore(dataStoreContext) + val storeAuthenticator = mockk() + val authenticatorRepository = AuthenticatorRepository(listOf(storeAuthenticator), appLoungeDataStore) + val appInstallProcessor = mock() + val blockedAppRepository = mock() + val systemAppsUpdatesRepository = mock() + val authData = AuthData(email = "user@example.com") + val applications = listOf() + + val inputData = Data.Builder() + .putBoolean(UpdatesWorker.IS_AUTO_UPDATE, false) + .build() + + whenever(workerContext.applicationContext).thenReturn(workerContext) + whenever(workerContext.getSharedPreferences(any(), any())).thenReturn(sharedPreferences) + whenever(workerContext.getString(any())).thenReturn("key") + whenever(workerContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(connectivityManager) + whenever(workerContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager) + whenever(workerContext.checkSelfPermission(any())).thenReturn(android.content.pm.PackageManager.PERMISSION_GRANTED) + whenever(connectivityManager.activeNetwork).thenReturn(network) + whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(networkCapabilities) + whenever(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)).thenReturn(true) + whenever(sharedPreferences.getBoolean(any(), any())).thenReturn(true) + + appLoungeDataStore.destroyCredentials() + appLoungeDataStore.saveUserType(User.GOOGLE) + appLoungeDataStore.saveAuthData(authData) + + val user = appLoungeDataStore.getUser() + val loginState = appLoungeDataStore.getLoginState() + + assertThat(user).isEqualTo(User.GOOGLE) + assertThat(loginState).isEqualTo(LoginState.AVAILABLE) + + every { storeAuthenticator.storeType } returns StoreType.PLAY_STORE + every { storeAuthenticator.isStoreActive() } returns true + coEvery { storeAuthenticator.logout() } returns Unit + coEvery { storeAuthenticator.login() } returns StoreAuthResult( + AuthObject.GPlayAuth(ResultSupreme.Success(authData), User.GOOGLE), + authData + ) + + whenever(params.inputData).thenReturn(inputData) + whenever(updatesManagerRepository.getUpdates()).thenReturn( + Pair(applications, ResultStatus.RETRY), + Pair(applications, ResultStatus.OK) + ) + + whenever(updatesManagerRepository.getUpdatesOSS()).thenReturn(Pair(applications, ResultStatus.OK)) + whenever(systemAppsUpdatesRepository.fetchUpdatableSystemApps(true)).thenReturn(Unit) + + val worker = UpdatesWorker( + workerContext, + params, + updatesManagerRepository, + appLoungeDataStore, + authenticatorRepository, + appInstallProcessor, + blockedAppRepository, + systemAppsUpdatesRepository + ) + + worker.doWork() + verify(updatesManagerRepository, times(2)).getUpdates() + coVerify { storeAuthenticator.login() } + } + + private fun createDataStore(context: Context): AppLoungeDataStore { + val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } + return AppLoungeDataStore(context, json) + } +} -- GitLab