1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.credentialmanager 17 18 import java.time.Instant 19 import android.graphics.drawable.Drawable 20 import com.android.credentialmanager.model.get.CredentialEntryInfo 21 import com.android.credentialmanager.model.get.ActionEntryInfo 22 import com.android.credentialmanager.model.get.AuthenticationEntryInfo 23 import com.android.credentialmanager.model.Request 24 import androidx.test.filters.SmallTest 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import org.junit.Test 27 import org.mockito.kotlin.mock 28 import org.junit.runner.RunWith 29 import com.android.credentialmanager.model.CredentialType 30 import com.google.common.truth.Truth.assertThat 31 import com.android.credentialmanager.ui.mappers.toGet 32 import com.android.credentialmanager.model.get.ProviderInfo 33 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries 34 35 /** Unit tests for [CredentialSelectorUiStateGetMapper]. */ 36 @SmallTest 37 @RunWith(AndroidJUnit4::class) 38 class CredentialSelectorUiStateGetMapperTest { 39 40 private val mDrawable = mock<Drawable>() 41 42 private val actionEntryInfo = 43 ActionEntryInfo( 44 providerId = "", 45 entryKey = "", 46 entrySubkey = "", 47 pendingIntent = null, 48 fillInIntent = null, 49 title = "title", 50 icon = mDrawable, 51 subTitle = "subtitle", 52 ) 53 54 private val authenticationEntryInfo = 55 AuthenticationEntryInfo( 56 providerId = "", 57 entryKey = "", 58 entrySubkey = "", 59 pendingIntent = null, 60 fillInIntent = null, 61 title = "title", 62 providerDisplayName = "", 63 icon = mDrawable, 64 isUnlockedAndEmpty = true, 65 isLastUnlocked = true 66 ) 67 68 private val passkeyCredentialEntryInfo = 69 createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName") 70 71 private val unknownCredentialEntryInfo = 72 createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2") 73 74 private val passwordCredentialEntryInfo = 75 createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName") 76 77 private val recentlyUsedPasskeyCredential = 78 createCredentialEntryInfo(credentialType = 79 CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName") 80 81 private val recentlyUsedPasswordCredential = 82 createCredentialEntryInfo(credentialType = 83 CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName") 84 85 private val credentialList1 = listOf( 86 passkeyCredentialEntryInfo, 87 passwordCredentialEntryInfo 88 ) 89 90 private val credentialList2 = listOf( 91 passkeyCredentialEntryInfo, 92 passwordCredentialEntryInfo, 93 recentlyUsedPasskeyCredential, 94 unknownCredentialEntryInfo, 95 recentlyUsedPasswordCredential 96 ) 97 98 @Test On primary screen, just one account returns SingleEntrynull99 fun `On primary screen, just one account returns SingleEntry`() { 100 val getCredentialUiState = Request.Get( 101 token = null, 102 resultReceiver = null, 103 providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true) 104 105 assertThat(getCredentialUiState).isEqualTo( 106 CredentialSelectorUiState.Get.SingleEntry(passkeyCredentialEntryInfo) 107 ) // prefer passkey over password for selected credential 108 } 109 110 @Test On primary screen, multiple accounts returns SingleEntryPerAccountnull111 fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() { 112 val getCredentialUiState = Request.Get( 113 token = null, 114 resultReceiver = null, 115 providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo, 116 unknownCredentialEntryInfo)))).toGet(isPrimary = true) 117 118 assertThat(getCredentialUiState).isEqualTo( 119 CredentialSelectorUiState.Get.MultipleEntryPrimaryScreen( 120 sortedEntries = listOf( 121 passkeyCredentialEntryInfo, // userName 122 unknownCredentialEntryInfo // userName2 123 ), 124 icon = mDrawable, 125 authenticationEntryList = listOf(authenticationEntryInfo) 126 )) // prefer passkey from account 1, then unknown from account 2 127 } 128 129 @Test On secondary screen, a MultipleEntry is returnednull130 fun `On secondary screen, a MultipleEntry is returned`() { 131 val getCredentialUiState = Request.Get( 132 token = null, 133 resultReceiver = null, 134 providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false) 135 136 assertThat(getCredentialUiState).isEqualTo( 137 CredentialSelectorUiState.Get.MultipleEntry( 138 listOf(PerUserNameEntries("userName", listOf( 139 passkeyCredentialEntryInfo, 140 passwordCredentialEntryInfo)) 141 ), 142 listOf(actionEntryInfo), 143 listOf(authenticationEntryInfo) 144 )) 145 } 146 147 @Test Returned multiple entry is sorted by credentialType and lastUsedTimeMillisnull148 fun `Returned multiple entry is sorted by credentialType and lastUsedTimeMillis`() { 149 val getCredentialUiState = Request.Get( 150 token = null, 151 resultReceiver = null, 152 providerInfos = listOf(createProviderInfo(credentialList1), 153 createProviderInfo(credentialList2))).toGet(isPrimary = false) 154 155 assertThat(getCredentialUiState).isEqualTo( 156 CredentialSelectorUiState.Get.MultipleEntry( 157 listOf( 158 PerUserNameEntries("userName", 159 listOf( 160 recentlyUsedPasskeyCredential, // from provider 2 161 passkeyCredentialEntryInfo, // from provider 1 or 2 162 passkeyCredentialEntryInfo, // from provider 1 or 2 163 recentlyUsedPasswordCredential, // from provider 2 164 passwordCredentialEntryInfo, // from provider 1 or 2 165 passwordCredentialEntryInfo, // from provider 1 or 2 166 )), 167 PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)), 168 ), 169 listOf(actionEntryInfo, actionEntryInfo), 170 listOf(authenticationEntryInfo, authenticationEntryInfo) 171 ) 172 ) 173 } 174 createCredentialEntryInfonull175 fun createCredentialEntryInfo( 176 userName: String, 177 credentialType: CredentialType = CredentialType.PASSKEY, 178 lastUsedTimeMillis: Long = 0L 179 ): CredentialEntryInfo = 180 CredentialEntryInfo( 181 providerId = "", 182 entryKey = "", 183 entrySubkey = "", 184 pendingIntent = null, 185 fillInIntent = null, 186 credentialType = credentialType, 187 rawCredentialType = "", 188 credentialTypeDisplayName = "", 189 providerDisplayName = "", 190 userName = userName, 191 displayName = "", 192 icon = mDrawable, 193 shouldTintIcon = false, 194 lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis), 195 isAutoSelectable = true, 196 entryGroupId = "", 197 isDefaultIconPreferredAsSingleProvider = false, 198 affiliatedDomain = "", 199 ) 200 201 fun createProviderInfo(credentials: List<CredentialEntryInfo> = listOf()): ProviderInfo = 202 ProviderInfo( 203 id = "providerInfo", 204 icon = mDrawable, 205 displayName = "displayName", 206 credentialEntryList = credentials, 207 authenticationEntryList = listOf(authenticationEntryInfo), 208 remoteEntry = null, 209 actionEntryList = listOf(actionEntryInfo) 210 ) 211 } 212