Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9828417d authored by Jonathan Klee's avatar Jonathan Klee
Browse files

test: add more unit tests

parent d3c61d39
Loading
Loading
Loading
Loading
Loading
+108 −2
Original line number Diff line number Diff line
@@ -29,9 +29,11 @@ import foundation.e.apps.data.login.microg.MicrogLoginManager
import foundation.e.apps.data.login.playstore.OauthAuthDataBuilder
import foundation.e.apps.data.preference.AppLoungeDataStore
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
@@ -49,7 +51,7 @@ import org.robolectric.annotation.Config
@Config(sdk = [30])
class MicrogLoginManagerTest {

    private val context: Application = RuntimeEnvironment.getApplication()
    private val context = RuntimeEnvironment.getApplication()

    @Test
    @Ignore("Flaky under mock AccountManager; covered by error-path tests")
@@ -196,9 +198,113 @@ class MicrogLoginManagerTest {
        assertTrue(result is MicrogLoginManager.FetchResult.Error)
    }

    private fun buildMicrogLoginManager(accountManager: AccountManager): MicrogLoginManager {
    @Test
    fun `login uses existing oauth token when refresh not needed`() = runBlocking {
        val accountManager = mock<AccountManager>()
        whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)))
            .thenReturn(emptyArray())
        val oauthAuthDataBuilder = mock<OauthAuthDataBuilder>()
        val appLoungeDataStore = mock<AppLoungeDataStore>()
        whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("oauth"))
        val authData = com.aurora.gplayapi.data.models.AuthData(email = "email")
        whenever(oauthAuthDataBuilder.build("oauth")).thenReturn(authData)

        val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login()

        assertEquals(authData, result)
    }

    @Test
    fun `login refreshes token and saves when prefix token is present`() = runBlocking {
        val account = Account("user@gmail.com", MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)
        val accountManager = mock<AccountManager>()
        whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)))
            .thenReturn(arrayOf(account))
        whenever(
            accountManager.getAuthToken(
                eq(account),
                eq(MicrogCertUtil.PLAY_AUTH_SCOPE),
                any<Bundle>(),
                eq(false),
                isNull(),
                isNull()
            )
        ).thenReturn(ImmediateAccountManagerFuture(Bundle().apply {
            putString(AccountManager.KEY_AUTHTOKEN, "token123")
        }))
        val oauthAuthDataBuilder = mock<OauthAuthDataBuilder>()
        val appLoungeDataStore = mock<AppLoungeDataStore>()
        whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("ya29.old"))
        whenever(appLoungeDataStore.emailData).thenReturn(flowOf(account.name))
        val authData = com.aurora.gplayapi.data.models.AuthData(email = account.name)
        whenever(oauthAuthDataBuilder.build("token123")).thenReturn(authData)

        val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login()

        assertEquals(authData, result)
        org.mockito.kotlin.verify(accountManager).invalidateAuthToken(account.type, "ya29.old")
        org.mockito.kotlin.verify(appLoungeDataStore).saveGoogleLogin(account.name, "token123")
        org.mockito.kotlin.verify(appLoungeDataStore).saveAasToken("")
    }

    @Test
    fun `login throws when token missing`() {
        val accountManager = mock<AccountManager>()
        whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)))
            .thenReturn(emptyArray())
        val oauthAuthDataBuilder = mock<OauthAuthDataBuilder>()
        val appLoungeDataStore = mock<AppLoungeDataStore>()
        whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf(""))

        val exception = assertThrows(IllegalStateException::class.java) {
            runBlocking {
                buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login()
            }
        }

        assertEquals(MicrogLoginManager.MICROG_TOKEN_MISSING, exception.message)
    }

    @Test
    fun `login throws when refresh requires user action`() {
        val account = Account("user@gmail.com", MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)
        val accountManager = mock<AccountManager>()
        whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)))
            .thenReturn(arrayOf(account))
        val intent = Intent("foundation.e.apps.ACTION_LOGIN")
        val resultBundle = Bundle().apply {
            putParcelable(AccountManager.KEY_INTENT, intent)
            classLoader = Intent::class.java.classLoader
        }
        whenever(
            accountManager.getAuthToken(
                eq(account),
                eq(MicrogCertUtil.PLAY_AUTH_SCOPE),
                any<Bundle>(),
                eq(false),
                isNull(),
                isNull()
            )
        ).thenReturn(ImmediateAccountManagerFuture(resultBundle))
        val oauthAuthDataBuilder = mock<OauthAuthDataBuilder>()
        val appLoungeDataStore = mock<AppLoungeDataStore>()
        whenever(appLoungeDataStore.oauthToken).thenReturn(flowOf("ya29.old"))
        whenever(appLoungeDataStore.emailData).thenReturn(flowOf(account.name))

        val exception = assertThrows(IllegalStateException::class.java) {
            runBlocking {
                buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, appLoungeDataStore).login()
            }
        }

        assertEquals(MicrogLoginManager.MICROG_TOKEN_REFRESH_FAILURE, exception.message)
    }

    private fun buildMicrogLoginManager(
        accountManager: AccountManager,
        oauthAuthDataBuilder: OauthAuthDataBuilder = mock(),
        appLoungeDataStore: AppLoungeDataStore = mock()
    ): MicrogLoginManager {
        return MicrogLoginManager(context, accountManager, oauthAuthDataBuilder, appLoungeDataStore)
    }

+51 −0
Original line number Diff line number Diff line
package foundation.e.apps.data.login.playstore

import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AuthHelper
import com.google.common.truth.Truth.assertThat
import foundation.e.apps.data.preference.AppLoungeDataStore
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.Properties

class OauthAuthDataBuilderTest {

    @Before
    fun setup() {
        mockkObject(AuthHelper)
    }

    @After
    fun tearDown() {
        unmockkObject(AuthHelper)
    }

    @Test
    fun build_usesEmailAndOauthToken() = runTest {
        val appLoungeDataStore = mockk<AppLoungeDataStore>()
        val properties = Properties()
        every { appLoungeDataStore.emailData } returns flowOf("user@gmail.com")
        val expected = AuthData(email = "user@gmail.com")
        every {
            AuthHelper.build(
                email = "user@gmail.com",
                token = "oauth-token",
                tokenType = AuthHelper.Token.AUTH,
                isAnonymous = false,
                properties = properties
            )
        } returns expected

        val builder = OauthAuthDataBuilder(appLoungeDataStore, properties)
        val result = builder.build("oauth-token")

        assertThat(result).isEqualTo(expected)
    }
}
+148 −0
Original line number Diff line number Diff line
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
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.login.core.AuthObject
import foundation.e.apps.data.login.microg.MicrogLoginManager
import foundation.e.apps.data.preference.AppLoungeDataStore
import foundation.e.apps.data.preference.AppLoungePreference
import kotlinx.coroutines.flow.flowOf
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test

class PlayStoreAuthenticatorTest {

    @Test
    fun login_returnsSavedAuthWhenValid() = runTest {
        val context = mockk<Context>(relaxed = true)
        val appLoungeDataStore = mockk<AppLoungeDataStore>()
        val appLoungePreference = mockk<AppLoungePreference>(relaxed = true)
        val authDataCache = mockk<AuthDataCache>()
        val authDataVerifier = mockk<PlayStoreAuthDataVerifier>()
        val googleLoginManager = mockk<GoogleLoginManager>(relaxed = true)
        val microgLoginManager = mockk<MicrogLoginManager>(relaxed = true)
        val anonymousLoginManager = mockk<AnonymousLoginManager>(relaxed = true)
        val aasTokenConverter = mockk<OauthToAasTokenConverter>(relaxed = true)
        val userProfileFetcher = mockk<UserProfileFetcher>(relaxed = true)

        val savedAuth = AuthData(email = "saved@example.com")
        val playResponse = mockk<PlayResponse>()
        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(
            context = context,
            appLoungeDataStore = appLoungeDataStore,
            appLoungePreference = appLoungePreference,
            authDataCache = authDataCache,
            authDataVerifier = authDataVerifier,
            googleLoginManager = googleLoginManager,
            microgLoginManager = microgLoginManager,
            anonymousLoginManager = anonymousLoginManager,
            aasTokenConverter = aasTokenConverter,
            userProfileFetcher = userProfileFetcher
        )

        val result = authenticator.login()

        assertThat(result.authObject).isInstanceOf(AuthObject.GPlayAuth::class.java)
        val authObject = result.authObject as AuthObject.GPlayAuth
        assertThat(authObject.result).isInstanceOf(ResultSupreme.Success::class.java)
        assertThat(authObject.result.data).isEqualTo(savedAuth)
        assertThat(result.authDataToPersist).isEqualTo(savedAuth)
    }

    @Test
    fun login_usesAnonymousLoginWhenNoSavedAuth() = runTest {
        val context = mockk<Context>(relaxed = true)
        val appLoungeDataStore = mockk<AppLoungeDataStore>()
        val appLoungePreference = mockk<AppLoungePreference>(relaxed = true)
        val authDataCache = mockk<AuthDataCache>()
        val authDataVerifier = mockk<PlayStoreAuthDataVerifier>()
        val googleLoginManager = mockk<GoogleLoginManager>(relaxed = true)
        val microgLoginManager = mockk<MicrogLoginManager>(relaxed = true)
        val anonymousLoginManager = mockk<AnonymousLoginManager>()
        val aasTokenConverter = mockk<OauthToAasTokenConverter>(relaxed = true)
        val userProfileFetcher = mockk<UserProfileFetcher>(relaxed = true)

        val authData = AuthData(email = "anon@example.com", isAnonymous = true)
        every { appLoungeDataStore.getUser() } returns User.ANONYMOUS
        every { authDataCache.getSavedAuthData() } returns null
        coEvery { anonymousLoginManager.login() } returns authData
        every { authDataCache.formatAuthData(any()) } answers { firstArg() }

        val authenticator = PlayStoreAuthenticator(
            context = context,
            appLoungeDataStore = appLoungeDataStore,
            appLoungePreference = appLoungePreference,
            authDataCache = authDataCache,
            authDataVerifier = authDataVerifier,
            googleLoginManager = googleLoginManager,
            microgLoginManager = microgLoginManager,
            anonymousLoginManager = anonymousLoginManager,
            aasTokenConverter = aasTokenConverter,
            userProfileFetcher = userProfileFetcher
        )

        val result = authenticator.login()

        assertThat(result.authObject).isInstanceOf(AuthObject.GPlayAuth::class.java)
        val authObject = result.authObject as AuthObject.GPlayAuth
        assertThat(authObject.result).isInstanceOf(ResultSupreme.Success::class.java)
        assertThat(authObject.result.data).isEqualTo(authData)
        assertThat(result.authDataToPersist).isEqualTo(authData)
    }

    @Test
    fun login_fetchesUserProfileForGoogle() = runTest {
        val context = mockk<Context>(relaxed = true)
        val appLoungeDataStore = mockk<AppLoungeDataStore>()
        val appLoungePreference = mockk<AppLoungePreference>(relaxed = true)
        val authDataCache = mockk<AuthDataCache>()
        val authDataVerifier = mockk<PlayStoreAuthDataVerifier>()
        val googleLoginManager = mockk<GoogleLoginManager>()
        val microgLoginManager = mockk<MicrogLoginManager>(relaxed = true)
        val anonymousLoginManager = mockk<AnonymousLoginManager>(relaxed = true)
        val aasTokenConverter = mockk<OauthToAasTokenConverter>()
        val userProfileFetcher = mockk<UserProfileFetcher>()

        val authData = AuthData(email = "user@gmail.com")
        val profile = mockk<UserProfile>()
        every { appLoungeDataStore.getUser() } returns User.GOOGLE
        every { appLoungeDataStore.playStoreAuthSource } returns flowOf("GOOGLE")
        every { appLoungeDataStore.aasToken } returns flowOf("aas-token")
        every { authDataCache.getSavedAuthData() } returns null
        coEvery { googleLoginManager.login() } returns authData
        every { authDataCache.formatAuthData(any()) } answers { firstArg() }
        coEvery { userProfileFetcher.fetch(authData) } returns profile

        val authenticator = PlayStoreAuthenticator(
            context = context,
            appLoungeDataStore = appLoungeDataStore,
            appLoungePreference = appLoungePreference,
            authDataCache = authDataCache,
            authDataVerifier = authDataVerifier,
            googleLoginManager = googleLoginManager,
            microgLoginManager = microgLoginManager,
            anonymousLoginManager = anonymousLoginManager,
            aasTokenConverter = aasTokenConverter,
            userProfileFetcher = userProfileFetcher
        )

        val result = authenticator.login()
        val authObject = result.authObject as AuthObject.GPlayAuth

        assertThat(authObject.result.data?.userProfile).isEqualTo(profile)
    }
}
+59 −0
Original line number Diff line number Diff line
package foundation.e.apps.data.login.playstore

import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.UserProfile
import com.aurora.gplayapi.helpers.UserProfileHelper
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.mockkConstructor
import io.mockk.unmockkConstructor
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test

class UserProfileFetcherTest {

    @Before
    fun setup() {
        mockkConstructor(UserProfileHelper::class)
    }

    @After
    fun tearDown() {
        unmockkConstructor(UserProfileHelper::class)
    }

    @Test
    fun fetch_returnsProfileOnSuccess() = runTest {
        val authData = AuthData(email = "user@gmail.com")
        val gPlayHttpClient = mockk<GPlayHttpClient>()
        val profile = mockk<UserProfile>()
        every { anyConstructed<UserProfileHelper>().using(gPlayHttpClient) } answers {
            this.invocation.self as UserProfileHelper
        }
        every { anyConstructed<UserProfileHelper>().getUserProfile() } returns profile

        val fetcher = UserProfileFetcher(gPlayHttpClient)
        val result = fetcher.fetch(authData)

        assertThat(result).isEqualTo(profile)
    }

    @Test
    fun fetch_returnsNullOnFailure() = runTest {
        val authData = AuthData(email = "user@gmail.com")
        val gPlayHttpClient = mockk<GPlayHttpClient>()
        every { anyConstructed<UserProfileHelper>().using(gPlayHttpClient) } answers {
            this.invocation.self as UserProfileHelper
        }
        every { anyConstructed<UserProfileHelper>().getUserProfile() } throws IllegalStateException("boom")

        val fetcher = UserProfileFetcher(gPlayHttpClient)
        val result = fetcher.fetch(authData)

        assertThat(result).isNull()
    }
}
+87 −1
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import androidx.test.core.app.ApplicationProvider
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.exceptions.InternalException
import com.google.common.truth.Truth.assertThat
import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.application.ApplicationDataManager
@@ -60,7 +61,7 @@ class PlayStoreRepositoryTest {

    @Test
    fun `getAppDetails retries after auth refresh on unauthorized`() = runTest {
        val authData = AuthData(email = "user@example.com")
        val authData = AuthData(email = "user@gmail.com")
        val app = App(packageName = "pkg.test", versionCode = 2)
        val authenticatorRepository = mock<AuthenticatorRepository>()

@@ -113,4 +114,89 @@ class PlayStoreRepositoryTest {
        mockitoVerify(authenticatorRepository).fetchAuthObjects(listOf(StoreType.PLAY_STORE))
        verify(exactly = 2) { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") }
    }

    @Test
    fun `getAppDetails throws AppNotFound on 404`() = runTest {
        val authData = AuthData(email = "user@gmail.com")
        val authenticatorRepository = mock<AuthenticatorRepository>()

        repository = PlayStoreRepository(
            context = context,
            gPlayHttpClient = gPlayHttpClient,
            authenticatorRepository = authenticatorRepository,
            applicationDataManager = applicationDataManager,
            playStoreSearchHelper = playStoreSearchHelper
        )

        whenever(authenticatorRepository.getGPlayAuthOrThrow()).thenReturn(authData)
        every {
            anyConstructed<AppDetailsHelper>().getAppByPackageName("missing.pkg")
        } throws GplayHttpRequestException(404, "not found")

        val exception = kotlin.test.assertFailsWith<InternalException.AppNotFound> {
            repository.getAppDetails("missing.pkg")
        }

        assertThat(exception).isInstanceOf(InternalException.AppNotFound::class.java)
    }

    @Test
    fun `getAppsDetails refreshes auth when anonymous and all versions zero`() = runTest {
        val authData = AuthData(email = "anon@example.com", isAnonymous = true)
        val staleApps = listOf(App(packageName = "pkg.test", versionCode = 0))
        val refreshedApps = listOf(App(packageName = "pkg.test", versionCode = 9))
        val authenticatorRepository = mock<AuthenticatorRepository>()

        repository = PlayStoreRepository(
            context = context,
            gPlayHttpClient = gPlayHttpClient,
            authenticatorRepository = authenticatorRepository,
            applicationDataManager = applicationDataManager,
            playStoreSearchHelper = playStoreSearchHelper
        )

        whenever(authenticatorRepository.getGPlayAuthOrThrow()).thenReturn(authData)
        whenever(authenticatorRepository.fetchAuthObjects(listOf(StoreType.PLAY_STORE)))
            .thenReturn(emptyList())
        every {
            anyConstructed<AppDetailsHelper>().getAppByPackageName(listOf("pkg.test"))
        } returns staleApps andThen refreshedApps

        val result = repository.getAppsDetails(listOf("pkg.test"))

        assertThat(result).hasSize(1)
        mockitoVerify(authenticatorRepository).fetchAuthObjects(listOf(StoreType.PLAY_STORE))
        verify(exactly = 2) {
            anyConstructed<AppDetailsHelper>().getAppByPackageName(listOf("pkg.test"))
        }
    }

    @Test
    fun `getAppsDetails filters invalid apps`() = runTest {
        val authData = AuthData(email = "user@gmail.com")
        val apps = listOf(
            App(packageName = "", versionCode = 3),
            App(packageName = "valid.pkg", versionCode = 0),
            App(packageName = "valid.pkg", versionCode = 5)
        )
        val authenticatorRepository = mock<AuthenticatorRepository>()

        repository = PlayStoreRepository(
            context = context,
            gPlayHttpClient = gPlayHttpClient,
            authenticatorRepository = authenticatorRepository,
            applicationDataManager = applicationDataManager,
            playStoreSearchHelper = playStoreSearchHelper
        )

        whenever(authenticatorRepository.getGPlayAuthOrThrow()).thenReturn(authData)
        every {
            anyConstructed<AppDetailsHelper>().getAppByPackageName(listOf("valid.pkg"))
        } returns apps

        val result = repository.getAppsDetails(listOf("valid.pkg"))

        assertThat(result).hasSize(1)
        assertThat(result.first().package_name).isEqualTo("valid.pkg")
    }
}
Loading