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

Commit 18473c18 authored by harinirajan's avatar harinirajan
Browse files

CredentialSelectorViewModel tests

Will test CredentialSelectorUiStateGetMapper and possibly
CredentialSelectorActivity in future CLs

Bug: 316039682
Test: Unit tests
Change-Id: Ifcfde26efc8d045f65175c6c9893992432671de7
parent 85457f97
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
package {
    // See: http://go/android-license-faq
    default_applicable_licenses: ["frameworks_base_license"],
}

android_robolectric_test {
    name: "CredentialSelectorTests",
    srcs: [
        "src/**/*.kt",
    ],
    // Include test libraries.
    instrumentation_for: "ClockworkCredentialManager",
    libs: [
        "androidx.test.runner",
        "androidx.test.ext.junit",
        "kotlinx_coroutines_android",
        "kotlinx_coroutines",
        "kotlinx-coroutines-core",
        "kotlinx_coroutines_test",
        "mockito-robolectric-prebuilt",
        "mockito-kotlin2",
        "CredentialManagerShared",
        "ClockworkCredentialManager",
        "framework_graphics_flags_java_lib",
    ],
    java_resource_dirs: ["config"],
    upstream: true,
}
+16 −0
Original line number Diff line number Diff line
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
sdk=NEWEST_SDK
+241 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.credentialmanager

import org.mockito.kotlin.whenever
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.Request
import androidx.test.filters.SmallTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.Before
import java.util.Collections.emptyList
import org.junit.runner.RunWith
import android.content.Intent
import com.android.credentialmanager.client.CredentialManagerClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import android.credentials.selection.BaseDialogResult
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.doReturn
import kotlinx.coroutines.Job
import org.junit.After
import org.robolectric.shadows.ShadowLooper
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.ExperimentalCoroutinesApi

/** Unit tests for [CredentialSelectorViewModel]. */
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class CredentialSelectorViewModelTest {
    private val testScope = TestScope(UnconfinedTestDispatcher())

    private val stateFlow: MutableStateFlow<Request?> = MutableStateFlow(Request.Create(null))
    private val credentialManagerClient = mock<CredentialManagerClient>{
        on { requests } doReturn stateFlow
    }
    private val mViewModel = CredentialSelectorViewModel(credentialManagerClient)
    private lateinit var job: Job

    val testEntryInfo =
        EntryInfo(
            providerId = "",
            entryKey = "",
            entrySubkey = "",
            pendingIntent = null,
            fillInIntent = null,
            shouldTerminateUiUponSuccessfulProviderResult = true)

    @Before
    fun setUp() {
      job = checkNotNull(mViewModel).uiState.launchIn(testScope)
    }

    @After
    fun teardown() {
        job.cancel()
    }

    @Test
    fun `Setting state to idle when receiving null request`() {
        stateFlow.value = null
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Idle)
    }

    @Test
    fun `Setting state to cancel when receiving Cancel request`() {
        stateFlow.value = Request.Cancel(appName = "appName", token = null)
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value)
            .isEqualTo(CredentialSelectorUiState.Cancel("appName"))
    }

    @Test
    fun `Setting state to create when receiving Create request`() {
        stateFlow.value = Request.Create(token = null)
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
    }

    @Test
    fun `Closing app when receiving Close request`() {
        stateFlow.value = Request.Close(token = null)
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Updates request`() {
        val intent = Intent()

        mViewModel.updateRequest(intent)

        verify(credentialManagerClient).updateRequest(intent)
    }

    @Test
    fun `Back on a single entry screen closes app`() {
        mViewModel.openSecondaryScreen()
        stateFlow.value = Request.Get(
            token = null,
            resultReceiver = null,
            finalResponseReceiver = null,
            providerInfos = emptyList())

        mViewModel.back()
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Back on a multiple entry screen gets us back to a primary screen`() {
        mViewModel.openSecondaryScreen()
        stateFlow.value = Request.Get(
            token = null,
            resultReceiver = null,
            finalResponseReceiver = null,
            providerInfos = emptyList())

        mViewModel.back()
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Back on create request state closes app`() {
        stateFlow.value = Request.Create(token = null)

        mViewModel.back()
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Back on close request state closes app`() {
        stateFlow.value = Request.Close(token = null)

        mViewModel.back()
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Back on cancel request state closes app`() {
        stateFlow.value = Request.Cancel(appName = "", token = null)

        mViewModel.back()
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Back on idle request state closes app`() {
        stateFlow.value = null

        mViewModel.back()
        ShadowLooper.idleMainLooper()

        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Cancel closes the app`() {
        mViewModel.cancel()
        ShadowLooper.idleMainLooper()

        verify(credentialManagerClient).sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Send entry selection result closes app and calls client method`() {
        whenever(credentialManagerClient.sendEntrySelectionResult(
            entryInfo = testEntryInfo,
            resultCode = null,
            resultData = null,
            isAutoSelected = false
        )).thenReturn(true)

        mViewModel.sendSelectionResult(
            entryInfo = testEntryInfo,
            resultCode = null,
            resultData = null,
            isAutoSelected = false)
        ShadowLooper.idleMainLooper()

        verify(credentialManagerClient).sendEntrySelectionResult(
            testEntryInfo, null, null, false
        )
        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
    }

    @Test
    fun `Send entry selection result does not close app on false return`() {
        whenever(credentialManagerClient.sendEntrySelectionResult(
            entryInfo = testEntryInfo,
            resultCode = null,
            resultData = null,
            isAutoSelected = false
        )).thenReturn(false)
        stateFlow.value = Request.Create(null)

        mViewModel.sendSelectionResult(entryInfo = testEntryInfo, resultCode = null,
            resultData = null, isAutoSelected = false)
        ShadowLooper.idleMainLooper()

        verify(credentialManagerClient).sendEntrySelectionResult(
            entryInfo = testEntryInfo,
            resultCode = null,
            resultData = null,
            isAutoSelected = false
        )
        assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -59,8 +59,10 @@ class CredentialSelectorViewModel @Inject constructor(
            isPrimaryScreen,
            shouldClose
        ) { request, isPrimary, shouldClose ->
            Log.d(TAG, "Request updated: " + request?.toString() +
                    " isClose: " + shouldClose.toString() +
                    " isPrimaryScreen: " + isPrimary.toString())
            if (shouldClose) {
                Log.d(TAG, "Request finished, closing ")
                return@combine Close
            }